coding A&D:關鍵路徑(AOE網)
1、AOE-網介紹
我們在學習拓撲排序(如果沒學,可以看看這篇部落格:拓撲排序詳解)的時候,已經接觸了什麼是AOV-網,AOV-網是優先考慮頂點的思路,而我們也同樣可以優先考慮邊,這個就是AOE-網的思路。
若在帶權的有向無環圖中,以頂點表示事件,以有向邊表示活動,邊上的權值表示活動的開銷(如該活動持續的時間),則此帶權的有向無環圖稱為AOE網。記住AOE-網只是比AOV-網多了一個邊的權重,而且AOV-網一般是設計一個龐大的工程各個子工程實施的先後順序,而我們的AOE-網就是不僅僅關係整個工程中各個子工程的實施的先後順序,同時也關係整個工程完成最短時間。
因此,通常在AOE網中列出完成預定工程計劃所需要進行的活動,每個活動計劃完成的時間,要發生哪些事件以及這些事件與活動之間的關係,從而可以確定該項工程是否可行,估算工程完成的時間以及確定哪些活動是影響工程進度的關鍵。
AOE-網還有一個特點就是:只有一個起點(入度為0的頂點)和一個終點(出度為0的頂點),並且AOE-網有兩個待研究的問題:
- 完成整個工程需要的時間
- 哪些活動是影響工程進度的關鍵
2、名詞解釋
- 關鍵路徑:AOE-網中,從起點到終點最長的路徑的長度(長度指的是路徑上邊的權重和)
- 關鍵活動:關鍵路徑上的邊
假設起點是vo,則我們稱從v0到vi的最長路徑的長度為vi的最早發生時間,同時,vi的最早發生時間也是所有以vi為尾的弧所表示的活動的最早開始時間,使用e(i)表示活動ai最早發生時間,除此之外,我們還定義了一個活動最遲發生時間,使用l(i)表示,不推遲工期的最晚開工時間。我們把e(i)=l(i)的活動ai稱為關鍵活動
所以,我們現在要求的就是每弧所對應的e(i)和l(i),求這兩個變數的公式是:
e(i)=ve(j)
l(i)=vl(k)-dut(<j,k>)
變數的介紹:
首先我們假設活動a(i)是弧<j,k>上的活動,j為弧尾頂點,k為弧頭(有箭頭的一邊),
ve(j)代表的是弧尾j的最早發生時間,
vl(k)代表的是弧頭k的最遲發生時間
dut(<j,k>)代表該活動要持續的時間,既是弧的權值
好了,先在我們知道了求e(i)和l(i)就必須先知道各個頂點的ve和vl了,所以下面我們就來求每個頂點ve和vl。其中,我們要知道ve和vl是要分開來求的。
先求ve,從ve(0)=0開始往前推(其實就是從起點開始往後,求各個頂點最早發生時間),公式如下:
ve(j)=Max{ve{i}+dut(<i,j>)};
<i,j>屬於T,j=1,2.....n-1,
其中T是所有以第j個頂點為頭的弧的集合。n為頂點的個數
下面我們繼續求:各個頂點的vl,vl是從vl(n-1)=ve(n-1)往後推進(其實就是從終點開始往前求各個頂點的最遲發生時間,其中終點的ve和vl是相等的)
vl(i)=Min{vl(j)-dut(<i,j>)}
<i,j>屬於S,i=n-2,n-3.....0
其中,S是所有以第i個頂點為尾的弧的集合
3、求關鍵路徑的步驟
- 輸入頂點數和邊數,已經各個弧的資訊建立圖
- 從源點v1出發,令ve[0]=0;按照拓撲序列往前求各個頂點的ve。如果得到的拓撲序列個數小於網的頂點數n,說明我們建立的圖有環,無關鍵路徑,直接結束程式
- 從終點vn出發,令vl[n-1]=ve[n-1],按逆拓撲序列,往後求其他頂點vl值
- 根據各個頂點的ve和vl求每個弧的e(i)和l(i),如果滿足e(i)=l(i),說明是關鍵活動。
4、求關鍵路徑的程式碼實現
- CriticalPath.h檔案的程式碼:
/************************************************************/
/* 程式作者:Willam */
/* 程式完成時間:2017/3/6 */
/* 有任何問題請聯絡:[email protected] */
/************************************************************/
//@儘量寫出完美的程式
#pragma once
//#pragma once是一個比較常用的C/C++雜注,
//只要在標頭檔案的最開始加入這條雜注,
//就能夠保證標頭檔案只被編譯一次。
/*
求解關鍵路徑問題,
必須是有向無環圖才有關鍵路徑
*/
#include<iostream>
#include<stack>
#include<string>
using namespace std;
//表結點
struct ArcNode {
int start; //弧尾的頂點的下標
int end; //弧頭的頂點的下標 ,有箭頭的一方
int weight; //弧的權重
ArcNode * next; //下一條弧
};
//頭結點
struct Vnode {
ArcNode * firstarc; //第一條依附在該該頂點的弧
string data;
};
class Graph_DG {
private:
int vexnum; //頂點個數
int edge; //邊的條數
Vnode * arc; //鄰接表
int *indegree; //各個頂點的入度情況
stack<int> List; //拓撲序列中各個邊的情況
int * ve; //記錄每個頂點的最早發生時間
int * vl; //記錄每個頂點最遲發生時間
public:
Graph_DG(int vexnum, int edge);
~Graph_DG();//解構函式
bool check_edge_value(int, int, int); //檢查邊的資訊是否合法
void createGraph();//建立一個新的圖
void print();//列印圖的鄰接表
bool topological_sort();
bool CriticalPath();
};
- CriticalPath.cpp檔案的程式碼
#include"CriticalPath.h"
Graph_DG::Graph_DG(int vexnum, int edge) {
/*
初始化一些基本的資訊,
包括邊和頂點個數,各個頂點入度陣列,鄰接表的等
*/
this->vexnum = vexnum;
this->edge = edge;
this->arc = new Vnode[this->vexnum];
this->indegree = new int[this->vexnum];
this->ve = new int[this->vexnum];
this->vl = new int[this->vexnum];
for (int i = 0; i < this->vexnum; i++) {
this->indegree[i] = 0;
this->ve[i] = 0;
this->arc[i].firstarc = NULL;
this->arc[i].data = "v" + to_string(i + 1);
}
}
//釋放記憶體空間
Graph_DG::~Graph_DG() {
ArcNode * p, *q;
for (int i = 0; i < this->vexnum; i++) {
if (this->arc[i].firstarc) {
p = this->arc[i].firstarc;
while (p) {
q = p->next;
delete p;
p = q;
}
}
}
delete[] this->arc;
delete[] this->indegree;
}
//判斷我們每次輸入的的邊的資訊是否合法
//頂點從1開始編號
bool Graph_DG::check_edge_value(int start, int end,int weight) {
if (start<1 || end<1 || start>vexnum || end>vexnum || weight < 0) {
return false;
}
return true;
}
void Graph_DG::createGraph() {
cout << "請輸入每條邊的起點和終點的編號(頂點從1開始編號)以及每條邊的權重" << endl;
int count = 0; //記錄初始化邊的條數
int start, end, weight;
while (count != this->edge) {
cin >> start >> end >> weight;
while (!check_edge_value(start, end, weight)) {
cout << "輸入的資訊不合法,請重新輸入:" << endl;
cin >> start >> end >> weight;
}
ArcNode * temp = new ArcNode;
temp->start = start-1;
temp->end = end-1;
temp->weight = weight;
temp->next = NULL;
//如果當前頂點的還沒有邊依附時,
++indegree[temp->end]; //對應的弧頭的頂點的入度加1
if (this->arc[start - 1].firstarc == NULL) {
this->arc[start - 1].firstarc = temp;
}
else {
ArcNode * now = this->arc[start - 1].firstarc;
while (now->next) {
now = now->next;
}//找到該連結串列的最後一個結點
now->next = temp;
}
++count;
}
}
void Graph_DG::print() {
cout << "圖的鄰接表為:" << endl;
int count = 0;
while (count != this->vexnum) {
cout << this->arc[count].data << " ";
ArcNode * temp = this->arc[count].firstarc;
while (temp) {
cout << "<"
<< this->arc[temp->start].data
<< ","
<< this->arc[temp->end].data
<< ">="
<< temp->weight
<< " ";
temp = temp->next;
}
cout << "^" << endl;
++count;
}
}
bool Graph_DG::topological_sort() {
cout << "圖的拓撲序列為:" << endl;
stack<int> s; //儲存入度為0的頂點下標
ArcNode * temp;
int i;
for (i = 0; i < this->vexnum; i++) {
if (!indegree[i]) s.push(i); //入度為0 ,則入棧
}
//count用於計算輸出的頂點個數
int count = 0;
while (!s.empty()) {//如果棧為空,退出迴圈
i = s.top(); //i等於棧頂的元素
s.pop();
cout << this->arc[i].data << " ";//輸出拓撲序列
temp = this->arc[i].firstarc;
this->List.push(i);
while (temp) {
if (!(--indegree[temp->end])) {//如果入度減少到為0,則入棧
s.push(temp->end);
}
//同時更新ve的值
if ((ve[i] + temp->weight) > ve[temp->end]) {
ve[temp->end] = ve[i] + temp->weight;
}
temp = temp->next;
}
++count;
}
if (count == this->vexnum) {
cout << endl;
return true;
}
cout << "此圖有環,無拓撲序列" << endl;
return false;//說明這個圖有環
}
bool Graph_DG::CriticalPath() {
if (!this->topological_sort()) {
cout << "此圖有環,無關鍵路徑" << endl;
return false;
}
cout << "圖的關鍵路徑為:" << endl;
//初始化vl的值都為ve[this->vexnum-1]
int k;
for (k = 0; k < this->vexnum; k++) {
vl[k] = ve[this->vexnum - 1];
}
ArcNode * temp;
while (!this->List.empty()) {
k = List.top();//從逆拓撲排序開始,求vl
List.pop();
temp = this->arc[k].firstarc;
while (temp) {
//dut<k,temp->end>,從以第k個頂點為弧尾集合中選擇一個最小的值
//來作為第i個頂點的最遲發生時間
if (vl[k] > (vl[temp->end] - temp->weight)) {
vl[k] = vl[temp->end] - temp->weight;
}
temp = temp->next;
}
}
int ee;
int el;
for (k = 0; k < this->vexnum; k++) {
temp= temp = this->arc[k].firstarc;
while (temp) {
ee = ve[k];
el = vl[temp->end] - temp->weight;
if (ee == el) {//這兩個值相等,說明它為關鍵活動:
cout << this->arc[k].data
<< "----"
<< this->arc[temp->end].data
<< "="
<< temp->weight
<< endl;
}
temp = temp->next;
}
}
}
- main.cpp檔案的程式碼
#include"CriticalPath.h"
//檢驗輸入邊數和頂點數的值是否有效,可以自己推算為啥:
//頂點數和邊數的關係是:((Vexnum*(Vexnum - 1)) / 2) < edge
bool check(int Vexnum, int edge) {
if (Vexnum <= 0 || edge <= 0 || ((Vexnum*(Vexnum - 1)) / 2) < edge)
return false;
return true;
}
int main() {
int vexnum; int edge;
cout << "輸入圖的頂點個數和邊的條數:" << endl;
cin >> vexnum >> edge;
while (!check(vexnum, edge)) {
cout << "輸入的數值不合法,請重新輸入" << endl;
cin >> vexnum >> edge;
}
Graph_DG graph(vexnum, edge);
graph.createGraph();
graph.print();
graph.CriticalPath();
system("pause");
return 0;
}
構造下圖:
輸入:
9 11
1 2 6
1 3 4
1 4 5
2 5 1
3 5 1
4 6 2
5 7 9
5 8 7
6 8 4
7 9 2
8 9 4
輸出:
從輸出可以看出,這個圖有兩條關鍵路徑: 第一條就是:
v1--v2--v5--v7--v9
- 1
第二條是:
v1--v2--v5--v8--v9
- 1
我們在構造下圖的關鍵路徑:
輸入:
6 8
1 2 3
1 3 2
2 4 2
2 5 3
3 4 4
3 6 3
4 6 2
5 6 1
輸出:
從輸出可以看出,這個圖有一條關鍵路徑:
v1--v3--v4--v6
- 1
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-8cccb36679.css" rel="stylesheet">
</div>
</article>