一:定義
(一)最短時間
我們要對一個流程圖獲得最短時間,就要分析他們的拓撲關系,並且找到當中的最關鍵的流程,這個流程的時間就是最短時間
(二)AOE網(Activity On Edge Network)
在一個表示工程的帶權有向圖中,用到的表示時間,用有向邊表示活動,用邊上權值表示活動的持續時間,這種有向圖的邊表示活動的網,我們稱之為AOE網
我們將AOE網中沒有入邊的頂點稱為源點或始點,沒有出邊的頂點稱為終點或者匯點
正常情況下,AOE網只有一個源點一個終點
例如下圖AOE網,
v0為源點,表示整個工程的開始,v9為終點,表示這個工程的結束。
頂點v0,v1,....v9分別表示事件
弧<v0,v1>,<v0,v2>,...,<v8,v9>都表示一個活動,用a0,a1,...,a12表示
補充:相比於AOV網
AOV中不在意邊的權值,不局限一個源點和一個終點,關注的是是否構成環
AOE關注邊的權值,來求得最短時間等信息,源點和終點都只有一個
(三)關鍵路徑
路徑上各個活動所持續的事件之和稱為路徑長度,從源點到終點具有最大長度的路徑叫關鍵路徑,在關鍵路徑上的活動叫關鍵活動
例如我們開始組裝時候,我們就要等到前面的所有裝備工作全部完成(按照最長路徑來算),才能開始我們的組裝任務。所以我們的關鍵路徑需要是最大長度的路徑
二:AOE和AOV(活動和事件|頂點與弧)
AOE網是表示工程流程的,所以它就具有明顯的工程的特性。
只有在某頂點所表示的事件發生后,從該頂點出發的各活動才能開始。
只有在進入某頂點的各活動都已經結束,該頂點所代表的事件才能發生。
AOE與AOV對比
雖然都是用來對工程建模,但是還是有很大不同。主要體現在:
AOV網是頂點表示活動的網,他只描述活動之間的制約更新,
AOE網是用邊表示活動的網,邊上的權值表示活動持續的時間
AOE網是要建立在活動之間制約關系沒有矛盾的基礎之上,再來分析完成整個工程至少需要多少時間,或者為縮短完成工程所需時間,應該加快哪些活動等問題
三:四個必要參數
在AOE網中頂點v表示時間,邊e表示活動
(一)事件最早發生時間etv(earliest time of vertex)
即頂點Vk的最早發生時間
(二)事件最晚發生時間ltv(lastest time of vertex)
即頂點Vk的最晚發生時間,也就是每個頂點對應的事件最晚需要開始的事件,超出此事件將會延誤整個工期
(三)活動的最早開工時間ete(earliest time of edge)
即弧ak的最早發生時間
(四)活動的最晚開工時間lte(lastest time if edge)
即弧的最晚發生時間,也就是不推遲工期的最晚開工時間
總結(重點):
我們可以由事件的最早發生時間和事件的最晚發生時間求出活動的最早和最晚開工時間。 由1,2可以求得3,4,然后在根據ete[k]是否與lte[k]相等來判斷ak是否是關鍵活動
活動的最早開始時間和活動的最晚開始時間,若是相等,就意味着此活動是關鍵活動
四:案例推導
(一)etv從左向右推導
其中P[k]表示所有到達頂點vk的弧的集合。比如P[1]就只有<v0,v1>,P[4]就有<v1,v4>,<v2,v4>是所有以頂點vk為弧頭的弧的集合
len<vi,vk>是弧<vi,vk>上的權值
例如:我們只推導v0-v4
etv[0]=0,因為k=0 etv[1]=6,etv[1]=max{etv[i]+len<vi,vk>},因為<vi,vk>屬於P[k],所以i=0,etv[i]=etv[0]+len<v0,v1>=6 etv[2]=4 --------------------------------------------------------------------上面可以看出來-------------------------------------------------------------------- etv[4]的推導,我們先看他的P[k]弧的集合,有<v1,v4>,<v2,v4>兩個,所以我們從{etv[1]+len<v1,v4>,etv[2]+len<v2,v4>}集合中選取最大的,就是etv[4]=etv[1]+len<v1,v4>=7
(二)ltv從右向左推導
是在我們得出etv之后,我們利用etv反推ltv。其實就是把拓撲序列倒過來繼續的,可以得出計算得到vk即求ltv[k]的最晚發生時間
其中n是頂點(事件個數),而我們的數組下標最大為n-1
其中S[k]是表示所有從頂點vk出發的弧的集合。例如下圖中S[6]就只是<v6,v8>的集合,S[4]就是<v4,v6>,<v4,v7>兩個弧的集合,是以頂點vk為弧尾的弧
len<vk,vj>是弧<vk,vj>上的權值
例如:我們推導上圖
ltv[8]=etv[8]=16 k=9-1,9是頂點個數 ltv[6]=min{ltv[j]-len<vk,vj>},其中以v6為弧尾的弧只有一條,<v6,v8>,是以ltv[6]=ltv[8]-len<v6,v8>=14 ltv[7]同上為12 ---------------------------------------------------------下面推導v4------------------------------------------------------- ltv[4]中以v4為弧尾的弧有:<v4,v6>和<v4,v7>,所以我們需要比較ltv[6]-len<v4,v6>,和ltv[7]-len<v4,v7> 其中ltv[6]-len<v4,v6>=7,ltv[7]-len<v4,v7>7,所以v4就是7
(三)ete:活動最早開工時間需要和etv事件最早發生時間結合
因為前面說了:只有在某頂點所表示的事件發生后,從該頂點出發的各活動才能開始。
所以我們要獲取某個活動的最早開始時間,我們就要知道,發起該活動的事件的最早開始時間,只有事件一開始,活動才進行,就相當於口哨一響起來,我們就開始跑步
活動a0,a1,a2全部由事件v0發起,所以我們的活動最早開始時間是elv[0] 活動a3的發起者事件是v1,所以我們最早開始時間就是elv[1]=6
活動a4的發起者事件是v2,所以活動最早開始時間是elv[2]=4
.....
(四)lte:活動最晚開工時間需要和ltv事件最晚發生時間結合(都是倒序獲得)
活動的最晚發生時間取決於弧所指向的事件的最晚開始時間-活動的持續時間
例如: a9指向v8,所以獲得a9的最晚開工時間是ltv[8]-len<a8>=16-2=14 a10也是指向v8,所以a10的最晚開工時間是ltv[8]-len<a10>=16-4=12 .....
總結:AOE網中活動的最早/最晚開工時間取決於事件的最早/最晚開始時間,所以我們求出etv和ltv就可以獲取ete和lte
五:算法實現
(一)求elv數組
int *etv, *ltv; //事件最早發生時間和最晚發生時間數組 int *stack2; //用於存儲拓撲序列的棧,因為幾乎是etv,ltv,ete,lte數組求取都需要拓撲序列,所以我們將它存儲在全局變量中 int top2; //用於stack2的指針,后面求關鍵路徑時需要
改進后的拓撲序列算法:在判斷是否有回路時,獲取全部拓撲序列放入全局棧stack2中,同時將獲取到etv數組內容
Status TopologicalSort(AdjGraphList AG) { EdgeNode* e; int i, j, k, gettop; int count = 0; //用於統計輸出頂點個數 int top = -1; //這是我們要創建的棧的指針 int *stack = (int*)malloc(sizeof(int)*AG.numVertexes); //這是我們創建的臨時棧 //最開始將所有入度為0的頂點入棧 for (i = 0; i < AG.numVertexes; i++) if (!AG.adjlist[i].in) stack[++top] = i; //初始化全局數組stack2,etv,ltv top2 = -1; etv = (int *)malloc(AG.numVertexes*sizeof(int)); for (i = 0; i < AG.numVertexes; i++) etv[i] = 0; //初始化為0 stack2 = (int *)malloc(sizeof(int)*AG.numVertexes); //下面進入主循環,直到棧中無數據結束 while (top != -1) { //出棧數據 gettop = stack[top--]; //出棧 count++; stack2[++top2] = gettop; //將彈出的棧頂序號壓入拓撲序列的棧中,將每一個頂點事件都入棧,變為拓撲序列 //對他出棧數據的所有鄰接點的入度減一 for (e = AG.adjlist[gettop].firstedge; e; e = e->next) { k = e->adjvex; if (!(--AG.adjlist[k].in)) stack[++top] = k; if ((etv[gettop] + e->weight) > etv[k]) //求各個頂點事件最早發生時間 etv[k] = etv[gettop] + e->weight; } } //進行判斷,若是count小於頂點數,則有環 if (count < AG.numVertexes) return ERROR; return OK; }
解釋:第三處的紅線代碼
if ((etv[gettop] + e->weight) > etv[k]) //求各個頂點事件最早發生時間 etv[k] = etv[gettop] + e->weight;
其中gettop是頂點v[i]的下標,而k就是頂點v[i]的鄰接點的下標k = e->adjvex;可以看做v[k],我們獲取的就是etv[k]
(二)求ltv數組
EdgeNode* e; int i, j, k, gettop; int ete, lte; //聲明獲得最早發生時間和最遲發生時間變量 TopologicalSort(AG); //求得拓撲序列,獲得etv數組和stack2數組 ltv = (int *)malloc(sizeof(int)*AG.numVertexes); //事件最晚發生時間數組 for (i = 0; i < AG.numVertexes; i++) ltv[i] = etv[AG.numVertexes - 1]; //全部初始化為倒序第一個 while (top2!=-1) { gettop = stack2[top2--]; //倒序獲取每個頂點,從終點開始,由於終點都沒有鄰接點,所以我們不會修改數據,始終保持etv[n-1],公式情況一 //根據求ltv的公式,我們開始補全ltv數組 for (e = AG.adjlist[gettop].firstedge; e;e=e->next) //這個for循環針對的是非終點,有出度邊,情況二 { k = e->adjvex; //k是他的下一個鄰接點的下標,我們會修改他,按照情況二 if (ltv[k] - e->weight < ltv[gettop]) //求各個頂點事件的最晚發生時間 ltv[gettop] = ltv[k] - e->weight; } }
(三)求ete,lte和關鍵活動
活動的最早開始時間和活動的最晚開始時間,若是相等,就意味着此活動是關鍵活動
根據前面求得的etv和ltv,我們可以獲取活動的最早和最晚開始時間,然后對其進行對比,即可判斷是否在關鍵路徑上
另外注意:這里的ete和lte是獲得是針對邊,所以我們操作的是出度邊集
for (i = 0; i < AG.numVertexes;i++) //i代表頂點下標 { for (e = AG.adjlist[i].firstedge; e;e=e->next) { k = e->adjvex; //獲取鄰接點 ete = etv[i]; //ete是由頂點i發起的活動,這是其活動最早開始時間 lte = ltv[k] - e->weight; //獲取其中某一個鄰接點的活動最遲發生時間,我們針對一個頂點會對其所有鄰接點進行獲取判斷 //我們獲取的是活動,是弧,弧的尾由i結點,頭由k決定 if (ete==lte) printf("<%c,%c> length:%d, ", AG.adjlist[i].data, AG.adjlist[k].data, e->weight); } }
(四)全部代碼

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #define MAXVEX 100 //最大頂點數 #define INFINITY 65535 //用0表示∞ #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 typedef int Status; typedef char VertexType; //頂點類型,字符型A,B,C,D... typedef int EdgeType; //邊上權值類型10,15,... //鄰接矩陣結構 typedef struct { VertexType vers[MAXVEX]; //頂點表 EdgeType arc[MAXVEX][MAXVEX]; //鄰接矩陣,可看作邊表 int numVertexes, numEdges; //圖中當前的頂點數和邊數 }MGraph; typedef struct EdgeNode //邊表結點 { int adjvex; int weight; struct EdgeNode* next; }EdgeNode; typedef struct VertexNode //頂點表結點 { int in; VertexType data; EdgeNode* firstedge; }VertexNode, AdjList[MAXVEX]; //鄰接表結構 typedef struct { AdjList adjlist; int numVertexes, numEdges; //圖中當前的頂點數和邊數 }AdjGraphList; int *etv, *ltv; //事件最早發生時間和最晚發生時間數組 int *stack2; //用於存儲拓撲序列的棧 int top2; //用於stack2的指針,后面求關鍵路徑時需要 //創建鄰接矩陣 void CreateMGraph(MGraph* G); //顯示鄰接矩陣 void showGraph(MGraph G); //鄰接矩陣轉鄰接表 void MGragp2AdjList(MGraph G, AdjGraphList* AG); //拓撲排序 Status TopologicalSort(AdjGraphList AG); //獲取求ete,lte和關鍵活動 void CriticalPath(AdjGraphList AG); //顯示事件的最早和最晚發生時間 void ShowVertexTime(int n); int main() { MGraph MG; AdjGraphList AG; CreateMGraph(&MG); showGraph(MG); MGragp2AdjList(MG, &AG); CriticalPath(AG); ShowVertexTime(AG.numVertexes); system("pause"); return 0; } //生成鄰接矩陣 void CreateMGraph(MGraph* G) { int i, j, k, w; G->numVertexes = 9; G->numEdges = 11; //讀入頂點信息 G->vers[0] = 'A'; G->vers[1] = 'B'; G->vers[2] = 'C'; G->vers[3] = 'D'; G->vers[4] = 'E'; G->vers[5] = 'F'; G->vers[6] = 'G'; G->vers[7] = 'H'; G->vers[8] = 'I'; //getchar(); //可以獲取回車符 for (i = 0; i < G->numVertexes; i++) for (j = 0; j < G->numVertexes; j++) G->arc[i][j] = INFINITY; //鄰接矩陣初始化 //創建了有向鄰接矩陣 G->arc[0][1] = 6; G->arc[0][2] = 4; G->arc[0][3] = 5; G->arc[1][4] = 1; G->arc[2][4] = 1; G->arc[3][5] = 2; G->arc[4][6] = 7; G->arc[4][7] = 5; G->arc[5][7] = 4; G->arc[6][8] = 2; G->arc[7][8] = 4; } //顯示鄰接矩陣 void showGraph(MGraph G) { for (int i = 0; i < G.numVertexes; i++) { for (int j = 0; j < G.numVertexes; j++) { if (G.arc[i][j] != INFINITY) printf("%5d", G.arc[i][j]); else printf(" 0"); } printf("\n"); } } void MGragp2AdjList(MGraph G, AdjGraphList* AG) { int i, j; EdgeNode* edge; AG->numEdges = G.numEdges; AG->numVertexes = G.numVertexes; for (i = 0; i < G.numVertexes; i++) { AG->adjlist[i].data = G.vers[i]; AG->adjlist[i].in = 0; AG->adjlist[i].firstedge = NULL; } for (i = 0; i < G.numVertexes; i++) { for (j = 0; j < G.numVertexes; j++) { if (G.arc[i][j] != INFINITY) { edge = (EdgeNode*)malloc(sizeof(EdgeNode)); edge->adjvex = j; edge->weight = G.arc[i][j]; edge->next = AG->adjlist[i].firstedge; AG->adjlist[i].firstedge = edge; AG->adjlist[j].in++; } } } } Status TopologicalSort(AdjGraphList AG) { EdgeNode* e; int i, j, k, gettop; int count = 0; //用於統計輸出頂點個數 int top = -1; //這是我們要創建的棧的指針 int *stack = (int*)malloc(sizeof(int)*AG.numVertexes); //這是我們創建的臨時棧 //最開始將所有入度為0的頂點入棧 for (i = 0; i < AG.numVertexes; i++) if (!AG.adjlist[i].in) stack[++top] = i; //初始化全局數組stack2,etv,ltv top2 = -1; etv = (int *)malloc(AG.numVertexes*sizeof(int)); for (i = 0; i < AG.numVertexes; i++) etv[i] = 0; //初始化為0 stack2 = (int *)malloc(sizeof(int)*AG.numVertexes); //下面進入主循環,直到棧中無數據結束 while (top != -1) { //出棧數據 gettop = stack[top--]; //出棧 count++; stack2[++top2] = gettop; //將彈出的棧頂序號壓入拓撲序列的棧中 //對他出棧數據的所有鄰接點的入度減一 for (e = AG.adjlist[gettop].firstedge; e; e = e->next) { k = e->adjvex; if (!(--AG.adjlist[k].in)) stack[++top] = k; if ((etv[gettop] + e->weight) > etv[k]) //求各個頂點事件最早發生時間 etv[k] = etv[gettop] + e->weight; } } //進行判斷,若是count小於頂點數,則有環 if (count < AG.numVertexes) return ERROR; return OK; } void CriticalPath(AdjGraphList AG) { EdgeNode* e; int i, j, k, gettop; int ete, lte; //聲明獲得最早發生時間和最遲發生時間變量 TopologicalSort(AG); //求得拓撲序列,獲得etv數組和stack2數組 ltv = (int *)malloc(sizeof(int)*AG.numVertexes); //事件最晚發生時間數組 for (i = 0; i < AG.numVertexes; i++) ltv[i] = etv[AG.numVertexes - 1]; //全部初始化為倒序第一個 while (top2!=-1) { gettop = stack2[top2--]; //倒序獲取每個頂點,從終點開始,由於終點都沒有鄰接點,所以我們不會修改數據,始終保持etv[n-1],公式情況一 //根據求ltv的公式,我們開始補全ltv數組 for (e = AG.adjlist[gettop].firstedge; e;e=e->next) //這個for循環針對的是非終點,有出度邊,情況二 { k = e->adjvex; //k是他的下一個鄰接點的下標,我們會修改他,按照情況二 if (ltv[k] - e->weight < ltv[gettop]) //求各個頂點事件的最晚發生時間 ltv[gettop] = ltv[k] - e->weight; } } for (i = 0; i < AG.numVertexes;i++) //i代表頂點下標 { for (e = AG.adjlist[i].firstedge; e;e=e->next) { k = e->adjvex; //獲取鄰接點 ete = etv[i]; //ete是由頂點i發起的活動,這是其活動最早開始時間 lte = ltv[k] - e->weight; //獲取其中某一個鄰接點的活動最遲發生時間,我們針對一個頂點會對其所有鄰接點進行獲取判斷 //我們獲取的是活動,是弧,弧的尾由i結點,頭由k決定 if (ete==lte) printf("<%c,%c> length:%d, ", AG.adjlist[i].data, AG.adjlist[k].data, e->weight); } } printf("\n"); } void ShowVertexTime(int n) { int i; printf("Vex "); for (i = 0; i < n; i++) { printf("v%d ", i); } printf("\netv "); for (i = 0; i < n; i++) { printf("%2d ", etv[i]); } printf("\nltv "); for (i = 0; i < n; i++) { printf("%2d ", ltv[i]); } }