前面我們簡要地介紹了AOE網和關鍵路徑的一些概念,本文接著對求解關鍵路徑程式的主要函數進行分析。現有一AOE網圖7-9-4所示,我們使用鄰接表格儲存體結構,注意與拓撲排序時鄰接表結構不同的地方在於,這裡弧表結點增加了weight域,用來儲存弧的權值。
求解事件的最早發生時間etv的過程,就是我們從頭至尾找拓撲序列的過程,因此在求關鍵路徑之前,需要先調用一次拓撲序列演算法的代碼來計算etv 和 拓撲序列列表,我們針對前面講過的AOV網與拓撲排序的程式進行改進,代碼如下(參考《大話資料結構》):
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
|
/* 拓撲排序,若GL無迴路,則輸出拓撲排序序列並返回1,若有迴路返回0。 */ bool TopologicalSort(GraphAdjList GL) { EdgeNode *pe; int i, k, gettop; int top = 0;/* 用於棧指標下標 */ int count = 0;/* 用於統計輸出頂點的個數 */ /* 建棧將入度為0的頂點入棧 */ int *stack = (int *)malloc(GL->numVertexes * sizeof(int)); for (i = 0; i < GL->numVertexes; i++) if (0 == GL->adjList[i].in) stack[++top] = i;/* 將入度為0的頂點入棧 */ top2 = 0; etv = (int *)malloc(GL->numVertexes * sizeof(int)); for (i = 0; i < GL->numVertexes; i++) etv[i] = 0; /* 初始化 */ stack2 = (int *)malloc(GL->numVertexes * sizeof(int)); cout << "TopologicalSort ..." << endl; while (top != 0) { gettop = stack[top--]; cout << GL->adjList[gettop].data << " -> "; count++; /* 輸出i號頂點,並計數 */ stack2[++top2] = gettop; /* 將彈出的頂點序號壓入拓撲序列的棧 */ for (pe = GL->adjList[gettop].firstedge; pe; pe = pe->next) { k = pe->adjvex; /* 將i號頂點的鄰接點的入度減1,如果減1後為0,則入棧 */ if (!(--GL->adjList[k].in)) stack[++top] = k; /* 求各頂時間點事件的最早發生時間etv值 */ if ((etv[gettop] + pe->weight) > etv[k]) etv[k] = etv[gettop] + pe->weight; } } cout << endl; if (count < GL->numVertexes) return false; else return true; } |
在程式開始處我們聲明了幾個全域變數:
int *etv,*ltv; /* 事件最早發生時間和最遲發生時間數組,全域變數 */
int *stack2; /* 用於儲存拓撲序列的棧 */
int top2; /* 用於stack2的指標 */
其中stack2用來儲存拓撲序列,以便後面求關鍵路徑時使用。
上面的拓撲排序函數中除了增加了第12~19行,29行,38~39行,其他跟前面講過的AOV網與拓撲排序沒什麼區別。
第12~19行初始化全域變數etv數組、top2和stack2的過程。第29行就是將本來要輸出的拓撲序列壓入全域棧stack2中。第38~39行很關鍵,是求etv數組的每一個元素的值,具體求值辦法參見AOE網和關鍵路徑。
下面來看求關鍵路徑的演算法代碼。
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
|
/* 求關鍵路徑,GL為有向網,輸出G的各項關鍵活動 */ void CriticalPath(GraphAdjList GL) { EdgeNode *pe; int i, j, k, gettop; int ete, lte;/* 聲明活動最早發生時間和最遲發生時間變數 */ TopologicalSort(GL);/* 求拓撲序列,計算數組etv和stack2的值 */ ltv = (int *)malloc(GL->numVertexes * sizeof(int)); /* 事件最早發生時間數組 */ for (i = 0; i < GL->numVertexes; i++) ltv[i] = etv[GL->numVertexes - 1];/* 初始化 */ cout << "etv : "; for (i = 0; i < GL->numVertexes; i++) cout << etv[i] << ' '; cout << endl; while (top2 != 0)/* 出棧是求ltv */ { gettop = stack2[top2--]; /* 求各頂時間點事件的最遲發生時間ltv值 */ for (pe = GL->adjList[gettop].firstedge; pe; pe = pe->next) { k = pe->adjvex; if (ltv[k] - pe->weight < ltv[gettop]) ltv[gettop] = ltv[k] - pe->weight; } } cout << "ltv : "; for (i = 0; i < GL->numVertexes; i++) cout << ltv[i] << ' '; cout << endl; /* 求ete,lte和關鍵活動 */ for (j = 0; j < GL->numVertexes; j++) { for (pe = GL->adjList[j].firstedge; pe; pe = pe->next) { k = pe->adjvex; ete = etv[j];/* 活動最早發生時間 */ lte = ltv[k] - pe->weight;/* 活動最遲發生時間 */ if (ete == lte) /* 兩者相等即在關鍵路徑上 */ cout << "<v" << GL->adjList[j].data << " - v" << GL->adjList[k].data << "> length: " << pe->weight << endl; } } } |
函數第7行調用求拓撲序列的函數,執行完畢後,全域數組etv和棧stack2 7-9-6所示,top2 = 10,也就是說,對於每個事件的最早發生時間,我們已經計算出來了。
第11~12行初始化全域變數ltv數組,因為etv[9] = 27,所以數組ltv值現在為全27。
第19~29行是計算ltv 數組的迴圈,具體方法參見AOE網和關鍵路徑。
當程式執行到第36行,etv和ltv數組的值7-9-9
比如etv[1] = 3, 而ltv[1] = 7,表示如果單位是天的話,哪怕v1整個事件在第7天才開始,也可以保證整個工程的按期完成,可以提前v1事件開始時間,但最早也得第3天開始。
第36~47行是求另兩個變數,活動最早開始時間ete和活動最晚開始時間lte,並對相同下標的它們進行比較。兩重迴圈嵌套是對鄰接表的頂點和每個頂點的弧表遍曆,具體方法參見AOE網和關鍵路徑,舉例來說,7-9-10,當j = 0時,當k = 2, ete = lte,
表示
弧<v0, v2> 是關鍵路徑,因此列印;當k = 1, ete != lte, 故弧<v0, v1> 不是關鍵路徑。
j = 1 一直到 j = 9為止,做法是完全相同的,最後輸出的結果如,最終關鍵路徑7-9-11所示。