之前我們介紹過,在一個工程中我們關心兩個問題:
(1)工程是否順利進行
(2)整個工程最短時間。
之前我們優先關心的是頂點(AOV),同樣我們也可以優先關心邊(同理有AOE)。(Activity On Edge Network)
看看百度百科上解釋:
AOE網:Activity on edge network
若在帶權的有向圖中,以頂點表示事件,以有向邊表示活動,邊上的權值表示活動的開銷(如該活動持續的時間),則此帶權的有向圖稱為AOE網。
如果用AOE網來表示一項工程,那么,僅僅考慮各個子工程之間的優先關系還不夠,更多的是關心整個工程完成的最短時間是多少;
哪些活動的延期將會影響整個工程的進度,而加速這些活動是否會提高整個工程的效率。
因此,通常在AOE網中列出完成預定工程計划所需要進行的活動,每個活動計划完成的時間,要發生哪些事件以及這些事件與活動之間的關系,
從而可以確定該項工程是否可行,估算工程完成的時間以及確定哪些活動是影響工程進度的關鍵。
很顯然,頂點表示事件,邊表示活動,邊的權則表示活動持續時間。
AOE一般用來估算工程的完成時間。
AOE表示工程的流程,把沒有入邊的稱為始點或者源點,沒有出邊的頂點稱為終點或者匯點。一般情況下,工程只有一個開始,一個結束,
所以正常情況下,AOE只有一個源點一個匯點。
AOV和AOE的區別:
1.AOV用頂點表示活動的網,描述活動之間的制約關系。
2.AOE用邊表示活動的網,邊上的權值表示活動持續的時間。
AOE 是建立在子過程之間的制約關系沒有矛盾的基礎之上,再來分析整個過程需要的時間。
AOE研究:
a.完成整個過程至少需要多長時間。
b.哪些活動影響工程的進度?
關鍵路徑:從源點到匯點具有最大長度的路徑。這個概念要清楚,一個工程不一定有一條關鍵路徑,可能會有多條。
關鍵活動:關鍵路徑上的活動(邊)。
針對上面AOE所關心的問題,要想縮短工程時間,就縮短關鍵路徑上的過程即可。(縮短后可能出現之前的關鍵路徑變成了非關鍵路徑)
由於AOE網上所有的活動是可以並行進行。這里舉一個例子,組裝一個變形金剛,需要頭,左膀右臂,身體,左腿右腿。
我們可以有兩種方法:1.先做好頭,做左手臂,做右手臂,做身體,做左腿,做右腿,然后再組裝。
2.同時做頭、手臂、身體、腿的部分,每有一個頭、兩個手臂、兩個腿和一個身體的時候,就可以組裝了。
方法1.如我們計算機中的串行運行。這樣時間開銷是累加的。
方法2.如我們計算機中的並行運行。這樣時間開銷可以立體應用。在此方法中,同時做各個部位時間不同,比如做頭時間最長,那么整個一個
變形金剛所用的時間決定與做頭的時間,如果做手臂的時間是做頭時間的一半,那么就是說做手臂的時間點可以在頭做了一半的時候。只要不超過這個
時間點,手臂部分是不會影響整個工程的進度的。
這里定義四個定義:前兩個針對頂點,后兩個針對邊
事件最早開始時間:頂點Vi最早發生的時間。
事件最晚開始時間:頂點Vi最晚發生的時間,超出則會延誤整個工期。
活動的最早開始時間:邊Eg最早發生時間。
活動的最晚開始時間:邊Eg最晚發生時間。不推遲工期的最晚開工時間。
下面這個例子說明一下:
說明:上圖中J中為49,是最早開始時間。這里可以看到最早開始時間,就是完要成該頂點,前面的所有到該點的路徑都要已經完成。所以取路徑最大那一條。
補充說明:
事件最早開始時間:例子圖中,F點,ACF(9) 和 ADF(19),到達F點時候,保證AC和AD都完成,這樣 F才能開始,所以F點的最早開始時間取最大值,即19.
可以看出,要求出到某一點的最早開始時間,則需要將匯於該點的所有路徑的時間求出來,取最大值。
事件最遲開始時間:這里是反着推,比如H點最遲開始時間,H到J 與 H到I到J兩條路徑,39 和 44,所謂最遲開始時間,就是超過這個時間就會影響整個工程進度,
而這個時間是時間點,是從源點工程開始計時的,所以對於H點,39和44是相對於源點,如果取44,則H-J這條路徑就會拖延,最遲開始時間選擇最小值。
關鍵路徑的特點:我們尋找關鍵路徑——關鍵路徑就是關鍵活動(頂點與頂點之間的邊組成),就是我們怎么判斷該頂點是否為關鍵活動(邊)的頂點,即判斷邊是否為關鍵活動。
前面定義過,關鍵路徑就是圖中從源點到匯點最長(權值最大)的路徑。
這條路徑就決定了整個工程的工期,這說明一個什么問題?
關鍵路徑上的頂點與頂點之間的活動的應該最早開始和最遲開始時間是相等的,
如果不等那么說明活動還有余額時間(在最早開始時間和最遲開始時間之間可以任選一個時間點開始),這說明還有其他活動是決定這個工程時間的,那就不是關鍵路徑了。
算法思想:
要准備兩個數組,a:最早開始時間數組etv,b:最遲開始時間數組。(針對頂點即事件而言)
1.從源點V0出發,令etv[0](源點)=0,按拓撲有序求其余各頂點的最早發生時間etv[i](1 ≤ i ≤ n-1)。同時按照上一章
拓撲排序的方法檢測是否有環存在。
2.從匯點Vn出發,令ltv[n-1] = etv[n-1],按拓撲排序求各個其余各頂點的最遲發生時間ltv[i](n-2 ≥ i ≥ 2);
3.根據各頂點的etv和ltv數組的值,求出弧(活動)的最早開工時間和最遲開工時間,求每條弧的最早開工時間和最遲開工時間是否相等,若相等,則是關鍵活動。
注意:1,2 完成點(事件)的最早和最遲。3根據事件來計算活動最早和最遲,從而求的該弧(活動)是否為關鍵活動。
關鍵代碼:
1.對圖進行拓撲排序,存儲了拓撲排序的順序,作為關鍵路徑的計算最遲開始時間的依據。
1 int TopplogicalSort(GraphAdjList *g) 2 { 3 int count=0; 4 eNode *e=NULL; 5 6 StackType *stack=NULL; 7 StackType top=0; 8 stack = (StackType *)malloc((*g).numVextexs*sizeof(StackType)); 9 10 int i; 11 12 //初始化拓撲序列棧 13 g_topOfStk = 0; 14 //開辟拓撲序列棧對應的最早開始時間數組 15 g_etv = (int *)malloc((*g).numVextexs*sizeof(int)); 16 //初始化數組 17 for (i=0;i<(*g).numVextexs;i++) 18 { 19 g_etv[i]=0; 20 } 21 //開辟拓撲序列的頂點數組棧 22 g_StkAfterTop = (int *)malloc(sizeof(int)*(*g).numVextexs); 23 24 25 26 27 for (i=0;i<(*g).numVextexs;i++) 28 { 29 if (!(*g).adjList[i].numIn) 30 { 31 stack[++top] = i; 32 // printf("init no In is %c\n",g_init_vexs[i]); 33 } 34 } 35 36 37 while(top) 38 { 39 int geter = stack[top]; 40 top--; 41 42 //把拓撲序列保存到拓撲序列棧,為后面做准備 43 g_StkAfterTop[g_topOfStk++] = geter; 44 45 printf("%c -> ",g_init_vexs[(*g).adjList[geter].idx]); 46 count++; 47 48 //獲取當前點出度的點,對出度的點的入度減一(當前點要出圖)。 49 //獲取當前頂點的出度點表 50 e = (*g).adjList[geter].fitstedge; 51 while(e) 52 { 53 int eIdx = e->idx; 54 //選取的出度點的入度減一 55 int crntIN = --(*g).adjList[eIdx].numIn; 56 if (crntIN == 0) 57 { 58 //如果為0,則說明該頂點沒有入度了,是下一輪的輸出點。 59 stack[++top] = eIdx; 60 // printf("running the vex is %c\n",g_init_vexs[e->idx]); 61 } 62 63 //求出關鍵路徑 64 if ((g_etv[geter] + e->weigh) > g_etv[eIdx]) 65 { 66 g_etv[eIdx] = g_etv[geter] + e->weigh; 67 } 68 69 e = e->next; 70 } 71 } 72 if (count < (*g).numVextexs)//如果圖本身就是一個大環,或者圖中含有環,這樣有環的頂點不會進棧而被打印出來。 73 { 74 return false; 75 } 76 else 77 { 78 printf("finish\n"); 79 return true; 80 } 81 82 }
2.關鍵路徑代碼:
1 void CriticalPath(GraphAdjList g) 2 { 3 int i; 4 int geter; 5 eNode *e = NULL; 6 g_topOfStk--; 7 //1.初始化最遲開始時間數組(匯點的最早開始時間(初值)) 8 g_ltv = (int *)malloc(sizeof(int)*g.numVextexs); 9 for (i=0;i<g.numVextexs;i++) 10 { 11 g_ltv[i] = g_etv[g.numVextexs-1]; 12 } 13 14 //2.求每個點的最遲開始時間,從匯點到源點推。 15 while (g_topOfStk) 16 { 17 //獲取當前出棧(反序)的序號 18 geter = g_StkAfterTop[g_topOfStk--]; 19 //對每個出度點 20 if (g.adjList[geter].fitstedge != NULL) 21 { 22 e = g.adjList[geter].fitstedge; 23 while(e != NULL) 24 { 25 int eIdx = e->idx; 26 if (g_ltv[eIdx] - e->weigh < g_ltv[geter]) 27 { 28 g_ltv[geter] = g_ltv[eIdx] - e->weigh; 29 } 30 e = e->next; 31 } 32 } 33 } 34 35 int ete,lte;//活動最早開始和最遲開始時間 36 37 38 39 printf("start:->"); 40 //3.求關鍵活動,即ltv和etv相等的 41 for (i=0;i<g.numVextexs;i++) 42 { 43 if (g.adjList[i].fitstedge) 44 { 45 e = g.adjList[i].fitstedge; 46 while(e) 47 { 48 int eIdx = e->idx; 49 //活動(i->eIdx)最早開始時間:事件(頂點) i最早開始時間 50 ete = g_etv[i]; 51 //活動(i->eIdx)最遲開始時間:事件(頂點) eIdx 最遲開始時間 減去 活動持續時間 52 lte = g_ltv[eIdx] - e->weigh; 53 if (ete == lte) 54 { 55 printf("(%c - %c)->",g_init_vexs[i],g_init_vexs[eIdx]); 56 } 57 e= e->next; 58 } 59 } 60 } 61 printf(" end\n"); 62 }
編程所用的圖:
拓撲排序結果:
過程:
1.從J開始,無后繼,不做任何事情;
2.G,G的ltv為27,ltv-weight = 27-2 < 27,所以G的ltv為25;
3.I,I的ltv為27,ltv-weight = 27 -3 < 27,所以I的ltv為24;
4.H,H的ltv為24(I的ltv),24-5 < 24,所以H 的ltv為19;
依次類推。。。
完成top排序和關鍵路徑后:
全局存放各個頂點的最早開始和最遲開始時間:
完整代碼:
1 // grp-top.cpp : 定義控制台應用程序的入口點。 2 // 3 // grp-top.cpp : 定義控制台應用程序的入口點。 4 // 5 6 #include "stdafx.h" 7 #include <stdlib.h> 8 9 10 #define MAXVEX 100 11 #define IFY 65535 12 13 14 typedef char VertexType; 15 typedef int EdgeType; 16 typedef int IdxType; 17 typedef int QueueType; 18 typedef int StackType; 19 20 21 //------- 22 int *g_etv = NULL; 23 int *g_ltv = NULL; 24 int *g_StkAfterTop; 25 int g_topOfStk; 26 27 28 ///--------------------------------------- 29 //邊節點 30 typedef struct EdgeNode{ 31 IdxType idx; 32 int weigh; 33 struct EdgeNode* next; 34 }eNode; 35 36 //頂點節點 37 typedef struct VexNode{ 38 int numIn; //入度數量 39 IdxType idx; 40 eNode *fitstedge; 41 }vNode; 42 43 //圖的集合:包含了一個頂點數組 44 typedef struct { 45 vNode adjList[MAXVEX]; 46 int numVextexs,numEdges; 47 }GraphAdjList; 48 49 ///----------------------------------- 50 /*VertexType g_init_vexs[MAXVEX] = {'A','B','C','D','E','F','G','H','I','J','K','L'}; 51 52 char *g_input[] = { 53 "A->B->C->D", 54 "B->E", 55 "C->F->I->J", 56 "D->E->I->J", 57 "E", 58 "F->K", 59 "G->F->H->K", 60 "H->I", 61 "I->J->L", 62 "J->E->K", 63 "K->L", 64 "L" 65 };*/ 66 67 ///----------------------------------- 68 VertexType g_init_vexs[MAXVEX] = {'A','B','C','D','E','F','G','H','I','J'}; 69 70 char *g_input[] = { 71 "A->B->C", 72 "B->D->E", 73 "C->D->F", 74 "D->E", 75 "E->G->H", 76 "F->H", 77 "G->J", 78 "H->I", 79 "I->J", 80 "J", 81 NULL 82 }; 83 84 char *g_input_weigh[] = { 85 "3,4",//A 86 "5,6",//B 87 "8,7",//C 88 "3",//D 89 "9,4",//E 90 "6",//F 91 "2",//G 92 "5",//H 93 "3",//I 94 " ",//J 95 NULL 96 }; 97 //=============================================================== 98 //隊列 99 100 //隊列節點 101 typedef struct Node { 102 QueueType data; 103 struct Node *next; 104 }QNode,*qQNode; 105 106 //隊列指示 107 typedef struct { 108 int length; 109 qQNode frnt,rear; 110 }spQueue; 111 112 void init_Queue(spQueue *Q) 113 { 114 (*Q).frnt = NULL; 115 (*Q).rear = NULL; 116 (*Q).length = 0; 117 } 118 bool isEmptyQueue(spQueue Q) 119 { 120 if (Q.length == 0) 121 { 122 return true; 123 } 124 return false; 125 } 126 //進隊 127 void unshiftQueue(spQueue *Q,QueueType elem) 128 { 129 //隊列空 130 if (isEmptyQueue(*Q)) 131 { 132 qQNode n = (qQNode)malloc(sizeof(QNode)); 133 n->data = elem; 134 n->next = NULL; 135 136 (*Q).frnt = n; 137 (*Q).rear = n; 138 (*Q).length = 1; 139 } 140 else 141 { 142 qQNode n = (qQNode)malloc(sizeof(QNode)); 143 n->data = elem; 144 n->next = NULL; 145 146 (*Q).rear->next = n; 147 148 (*Q).rear = n; 149 (*Q).length++; 150 } 151 } 152 153 //出隊 154 QueueType shiftQueue(spQueue *Q) 155 { 156 if (isEmptyQueue(*Q)) 157 { 158 printf("Warning:Queue is empty!!!\n"); 159 return NULL; 160 } 161 if ((*Q).length == 1) 162 { 163 QueueType sh = (*Q).frnt->data; 164 (*Q).frnt = NULL; 165 (*Q).rear = NULL; 166 (*Q).length = 0; 167 return sh; 168 } 169 QueueType sh = (*Q).frnt->data; 170 (*Q).frnt = (*Q).frnt->next; 171 (*Q).length--; 172 173 return sh; 174 } 175 176 //打印隊列 177 void prt_que(spQueue que) 178 { 179 if (isEmptyQueue(que)) 180 { 181 return ; 182 } 183 qQNode pos = que.frnt; 184 while(que.rear->next != pos && pos != NULL) 185 { 186 printf(" %d ",pos->data); 187 pos = pos->next; 188 } 189 printf("\n"); 190 } 191 //=============================================================== 192 193 ///------- 194 //由節點找節點的序號 195 IdxType strFindIdx(char ch) 196 { 197 int i=0; 198 VertexType *p = g_init_vexs; 199 while(p != NULL) 200 { 201 if(*p == ch) 202 { 203 return i; 204 } 205 p++; 206 i++; 207 } 208 return i; 209 } 210 211 //由序號找節點 212 VertexType idxFindStr(IdxType i) 213 { 214 return g_init_vexs[i]; 215 } 216 217 void prt_strings(char *p) 218 { 219 char *pos = p; 220 while (NULL != *pos) 221 { 222 printf("%c",*pos); 223 pos++; 224 } 225 printf("\n"); 226 } 227 228 void prt_strArrays(char *p[],int num) 229 { 230 char **pos = p; 231 int i=0; 232 while( *pos != NULL && i < num) 233 { 234 prt_strings(*pos); 235 pos++; 236 i++; 237 } 238 } 239 240 //自己規定:頂點只能是大寫。 241 bool isVexter(char p) 242 { 243 if (p>='A' && p<='Z') 244 { 245 return true; 246 } 247 return false; 248 } 249 250 bool isNumeric(char p) 251 { 252 if (p >= '0' && p <= '9') 253 { 254 return true; 255 } 256 return false; 257 } 258 259 void init_GrapAdjList(GraphAdjList *g,char **str,char **wstr) 260 { 261 char **pos = str; 262 263 int cnt=0; 264 int vcnt = 0; 265 char **wpos = wstr;//weight value 266 267 //入度清零 268 int i; 269 for (i=0;i<MAXVEX;i++) 270 { 271 (*g).adjList[i].numIn = 0; 272 } 273 274 while (*pos != NULL) //g_input的每行的首指針 275 { 276 int i=0; 277 while(**pos != NULL) //g_input的每行字母 278 { 279 if(isVexter(**pos)) //判斷是否為頂點(我規定‘A’-‘Z’之間為頂點標志) 280 { 281 if (i == 0) //建立頂點的節點 282 { 283 (*g).adjList[cnt].idx = strFindIdx(**pos); 284 (*g).adjList[cnt].fitstedge = NULL; 285 286 i=1; 287 } 288 else if(i == 1) //建立第一個邊的節點 289 { 290 eNode* n = (eNode*)malloc(sizeof(eNode)); 291 n->idx = strFindIdx(**pos); 292 n->next = NULL; 293 294 //weight 295 while (!isNumeric(**wpos)) 296 { 297 (*wpos)++; 298 } 299 n->weigh = **wpos-'0'; 300 (*wpos)++; 301 302 (*g).adjList[cnt].fitstedge = n; 303 i=2; 304 305 //添加入度 306 int iidx = strFindIdx(**pos); 307 (*g).adjList[iidx].numIn++; 308 } 309 else //邊節點連接到前一個邊節點上 310 { 311 eNode* n = (eNode*)malloc(sizeof(eNode)); 312 n->idx = strFindIdx(**pos); 313 n->next = NULL; 314 315 //weight 316 while (!isNumeric(**wpos)) 317 { 318 (*wpos)++; 319 } 320 n->weigh = **wpos-'0'; 321 (*wpos)++; 322 323 //first splist 324 eNode *r = (*g).adjList[cnt].fitstedge; 325 while (r->next != NULL) 326 { 327 r = r->next; 328 } 329 r->next = n; 330 331 //添加入度 332 int iidx = strFindIdx(**pos); 333 (*g).adjList[iidx].numIn++; 334 } 335 } 336 (*pos)++; 337 } 338 339 wpos++; 340 cnt++; 341 pos++; 342 343 } 344 (*g).numVextexs = cnt; 345 } 346 347 int TopplogicalSort(GraphAdjList *g) 348 { 349 int count=0; 350 eNode *e=NULL; 351 352 StackType *stack=NULL; 353 StackType top=0; 354 stack = (StackType *)malloc((*g).numVextexs*sizeof(StackType)); 355 356 int i; 357 358 //初始化拓撲序列棧 359 g_topOfStk = 0; 360 //開辟拓撲序列棧對應的最早開始時間數組 361 g_etv = (int *)malloc((*g).numVextexs*sizeof(int)); 362 //初始化數組 363 for (i=0;i<(*g).numVextexs;i++) 364 { 365 g_etv[i]=0; 366 } 367 //開辟拓撲序列的頂點數組棧 368 g_StkAfterTop = (int *)malloc(sizeof(int)*(*g).numVextexs); 369 370 371 372 373 for (i=0;i<(*g).numVextexs;i++) 374 { 375 if (!(*g).adjList[i].numIn) 376 { 377 stack[++top] = i; 378 // printf("init no In is %c\n",g_init_vexs[i]); 379 } 380 } 381 382 383 while(top) 384 { 385 int geter = stack[top]; 386 top--; 387 388 //把拓撲序列保存到拓撲序列棧,為后面做准備 389 g_StkAfterTop[g_topOfStk++] = geter; 390 391 printf("%c -> ",g_init_vexs[(*g).adjList[geter].idx]); 392 count++; 393 394 //獲取當前點出度的點,對出度的點的入度減一(當前點要出圖)。 395 //獲取當前頂點的出度點表 396 e = (*g).adjList[geter].fitstedge; 397 while(e) 398 { 399 int eIdx = e->idx; 400 //選取的出度點的入度減一 401 int crntIN = --(*g).adjList[eIdx].numIn; 402 if (crntIN == 0) 403 { 404 //如果為0,則說明該頂點沒有入度了,是下一輪的輸出點。 405 stack[++top] = eIdx; 406 // printf("running the vex is %c\n",g_init_vexs[e->idx]); 407 } 408 409 //求出關鍵路徑 410 if ((g_etv[geter] + e->weigh) > g_etv[eIdx]) 411 { 412 g_etv[eIdx] = g_etv[geter] + e->weigh; 413 } 414 415 e = e->next; 416 } 417 } 418 if (count < (*g).numVextexs)//如果圖本身就是一個大環,或者圖中含有環,這樣有環的頂點不會進棧而被打印出來。 419 { 420 return false; 421 } 422 else 423 { 424 printf("finish\n"); 425 return true; 426 } 427 428 } 429 void CriticalPath(GraphAdjList g) 430 { 431 int i; 432 int geter; 433 eNode *e = NULL; 434 g_topOfStk--; 435 //1.初始化最遲開始時間數組(匯點的最早開始時間(初值)) 436 g_ltv = (int *)malloc(sizeof(int)*g.numVextexs); 437 for (i=0;i<g.numVextexs;i++) 438 { 439 g_ltv[i] = g_etv[g.numVextexs-1]; 440 } 441 442 //2.求每個點的最遲開始時間,從匯點到源點推。 443 while (g_topOfStk) 444 { 445 //獲取當前出棧(反序)的序號 446 geter = g_StkAfterTop[g_topOfStk--]; 447 //對每個出度點 448 if (g.adjList[geter].fitstedge != NULL) 449 { 450 e = g.adjList[geter].fitstedge; 451 while(e != NULL) 452 { 453 int eIdx = e->idx; 454 if (g_ltv[eIdx] - e->weigh < g_ltv[geter]) 455 { 456 g_ltv[geter] = g_ltv[eIdx] - e->weigh; 457 } 458 e = e->next; 459 } 460 } 461 } 462 463 int ete,lte;//活動最早開始和最遲開始時間 464 465 466 467 printf("start:->"); 468 //3.求關鍵活動,即ltv和etv相等的 469 for (i=0;i<g.numVextexs;i++) 470 { 471 if (g.adjList[i].fitstedge) 472 { 473 e = g.adjList[i].fitstedge; 474 while(e) 475 { 476 int eIdx = e->idx; 477 //活動(i->eIdx)最早開始時間:事件(頂點) i最早開始時間 478 ete = g_etv[i]; 479 //活動(i->eIdx)最遲開始時間:事件(頂點) eIdx 最遲開始時間 減去 活動持續時間 480 lte = g_ltv[eIdx] - e->weigh; 481 if (ete == lte) 482 { 483 printf("(%c - %c)->",g_init_vexs[i],g_init_vexs[eIdx]); 484 } 485 e= e->next; 486 } 487 } 488 } 489 printf(" end\n"); 490 } 491 492 493 int _tmain(int argc, _TCHAR* argv[]) 494 { 495 GraphAdjList grp; 496 printf("print Matix: of Vextexs:\n"); 497 prt_strArrays(g_input,10); 498 printf("print Matix: of Weigh:\n"); 499 prt_strArrays(g_input_weigh,10); 500 501 init_GrapAdjList(&grp,g_input,g_input_weigh); 502 printf("Top sort:\n"); 503 if (!TopplogicalSort(&grp)) 504 { 505 printf("grp wrong!\n"); 506 } 507 508 CriticalPath(grp); 509 510 getchar(); 511 return 0; 512 }
測試結果: