1.AOE網算法
一個帶權的有向無環圖,頂點表示事件,弧表示活動,權表示活動持續的時間。一般AOE網用來估算工程的完成時間。數據庫
例如:後端
上圖是一個假想的有11項活動的AOE網,有9個事件,每一個事件表示在它以前的活動已經完成,在它以後的活動能夠開始。如V4表示a3已經完成,a6能夠開始。數組
一般整個工程只有一個入度爲零的點(源點)和一個出度爲零的點(匯點)。測試
也許你能夠定義一個工程或者一件事有多個初始態,或者多個結束態,但那增長了問題的複雜性,不是最小意義的分割,爲了方便研究通常把某件事分清楚,再定義一個個事件和活動。ui
AOE網中有些活動能夠並行進行,如圖中的a2和a5,若某個活動早已完成,另外一個活動還未開始,則這個工程不能算結束。也許你會腦洞大開,先完成的人不能夠幫助那些作的慢的人嘛,固然能夠,只是那樣就再也不是咱們初始定義時的AOE網,現實中分工明確,每一個團隊或者個體都有本身的任務,通常獨立完成,因此工程完成的最先時間是從開始點到完成點的最長路徑的長度。額,注意一下,路徑長度不是弧的個數而是弧所表明的活動的持續時間。也許你還會想,若是那些作的慢的人再一次放緩進度,那活動持續時間不是更長了麼,額,這個嘛,犯了一個邏輯上的錯誤。咱們在規劃時必須有一些肯定的值,若是什麼規劃時連預估的範圍都是不肯定的,那估算也沒有任何意義或者說根本沒法進行。編碼
假設咱們已經估算了某個工程設計到的全部活動的持續時間,那麼工程完成的最短期就是最長路徑的長度,稱之爲關鍵路徑。
spa
爲了便於描述,咱們定義一些符號:設計
e(i)表示活動ai的最先開始時間
code
l(i)表示活動ai的最遲開始時間,就是在不推遲整個工程的前提下,某個活動最遲必須開始時間(這個概念好像有點抽象,你想,一個團隊中有些人作的快有些人作的慢,作的快的人就能夠稍作休息,只須要不在慢的人以後作完就能夠了,心疼那些作的慢的人,沒有休息)
l(i)-e(i)表示活動ai的時間餘量
l(i)=e(i)的活動稱之爲關鍵活動
例如活動a5的最先開始時間是4,最遲開始時間是5
那麼如何辨別關鍵路徑呢?
關鍵路徑嘛,就是不能再拖拉的活動了,不然原本就由於該活動完成速度較慢而延長的工程完成時間會更長。也就是作關鍵路徑上的活動的人不能中途休息,也就是最先開始時間和最遲開始時間一致。那麼如何求 l(i)和e(i)。這時候你應該回憶AOE網中事件的定義:若到達某個頂點則表示它前面的活動已經完成,以這個頂點爲弧尾的弧才能夠開始。這點不難理解,若是大家要作一個電子標籤系統,作客戶端界面和後端數據庫的人總得等作驅動的人提供接口。因此咱們能夠先求事件最先發生時間ve(j)和最遲發生時間vl(j),若是活動ai由弧<j,k>表示,其持續時間記爲dut(<j,k>),則有以下關係:
e(i)=ve(j)
l(i)=vl(k)-dut(<j,k>)
求ve(j)和vl(j)分兩步:
⑴從ve(0)=0開始向前遞推
ve(j)=Max{ve(i)+dut(<i,j>)} <i,j>∈T,j=1,2,...,n-1 T是全部以第j個頂點爲弧頭的弧的集合
PS:所謂的第j個頂點不過是咱們存儲圖是給頂點標的序號;求最先開始時間嘛,要等最慢的那個活動完成才能夠開始,因此就求在j頂點的全部直接前驅加上到第j個頂點的時間中完成的最長的那個;也許你會問那前驅的前驅怎麼辦,其實這裏反應了一個算法思想,動態規劃。咱們能夠簡單用個反證法證實一下在這裏的正確性。
假設前驅記錄的最先時間不是最先時間,也就是說存在從開始點到該店的更長路徑,然而,這個不可能的,從定義能夠知道,咱們在求最先時間過程當中每次都是選的最長路徑,若是存在從開始點到該點的更長路徑,便與咱們的構造定義矛盾,因此前驅記錄的最先時間是最先時間。
(2)從vl(n-1)=ve(n-1)起向後遞推
vl(i)=Min{vl(j)-dut(<i,j>)} <i,j>∈S S是全部以第i個頂點爲弧尾的弧的集合
PS:從結束點開始(工程完成的時間,也就是最長路徑長度),對於某個事件,求最遲開始時間嘛,你想一下這個事件表明前面(前驅)活動的已經完成,後面(後繼)的才能夠開始。那麼這個事件的最遲發生時間加上到直接後繼的時間不能超過直接後繼的最遲發生時間,若是超過了,直接後繼就要繼續延遲。原本就拖了,再拖了,工程的時間只會更長,也就是你即便作的快,也不能無限等,總得有個限度。那麼這個限度是什麼呢,你要保證以你的工做爲基礎作的最慢的人(直接後繼)可以在下一個事件的最遲時間以前(能夠等於)完成。因此你地預留最的最慢的人的工做時間(活動時間)。
2.具體編碼實現
從上述分析可知,要求某個事件的最先發生時間,就必須先求得其全部前驅的最先發生時間,那麼問題又來了,怎麼求某個事件的前驅,這就要用到拓撲排序了;
要求某個事件的最遲發生時間,就必須先求得其全部後繼的最遲發生時間,怎麼求某個事件的後繼呢,用逆拓撲排序就能夠了,也就是拓撲序列反過來。
#include<stdio.h> #include<stdlib.h> typedef struct _arcNode { int adjvex; int weight; struct _arcNode *nextarc; }arcNode;//定義有向弧,包含弧頭標號,弧的權重(活動持續時間) typedef struct _vNode { char data; arcNode *firstarc; }vNode;//定義頂點,包含頂點信息,指向以該頂點爲弧尾的第一條弧 #define MaxVertexNum 100//最大頂點數 typedef vNode adjList[MaxVertexNum]; typedef struct _adjGraph { adjList ver; int e, n; }adjGraph;//定義鄰接表 void createAdjGraph(adjGraph *G) { scanf("%d,%d", &G->n, &G->e); getchar(); for (int i = 0; i < G->n; i++) { scanf("%c ", &G->ver[i].data); G->ver[i].firstarc = NULL; } int a, b, w; for (int i = 0; i < G->e; i++) { scanf("%d,%d,%d", &a, &b, &w); arcNode *tmp = (arcNode*)malloc(sizeof(arcNode)); tmp->weight = w; if (G->ver[a].firstarc == NULL) { tmp->adjvex = b; tmp->nextarc = NULL; G->ver[a].firstarc = tmp; } else { tmp->adjvex = b; tmp->nextarc = G->ver[a].firstarc; G->ver[a].firstarc = tmp; } } }//建立鄰接表 typedef struct _topoSequenceS { int verNum[MaxVertexNum]; int top; }topoSequenceS;//定義拓撲排序的存儲結構,拓撲排序時,先輸出的頂點標號入棧;你拓撲排序倒過來就能夠 topoSequenceS *topoS = (topoSequenceS*)malloc(sizeof(topoSequenceS)); void findInDegree(adjGraph *G, int *indegree) { for (int i = 0; i < G->n; i++) { arcNode *arcPos = G->ver[i].firstarc; while (arcPos != NULL) { indegree[arcPos->adjvex] += 1; arcPos = arcPos->nextarc; } } }//查詢每一個頂點的入度,indegree的下標對應頂點標號;搜索時每遇到以某個頂點爲弧頭的弧對應弧頭的入度就加一 bool topologicalSort(adjGraph *G,int *ve) { int *indegree = (int*)malloc(sizeof(int)*G->n); for (int i = 0; i < G->n; i++) indegree[i] = 0;//初始化每一個頂點的入度 findInDegree(G, indegree); int *tmpS = (int*)malloc(sizeof(int)*G->n);//臨時存儲入度爲零且未輸出的頂點 int top = -1; for (int i = 0; i < G->n; i++) { if (!indegree[i])tmpS[++top] = i; } for (int i = 0; i < G->n; i++) ve[i] = 0;//初始化每一個頂點的最先開始時間 topoS->top = -1;//初始化存儲拓撲序列的棧 int i = 0; while (top != -1)//拓撲排序 { i = topoS->verNum[++topoS->top] = tmpS[top--];//入度爲零,彈出,也就是輸出對應頂點 arcNode *arcPos = G->ver[i].firstarc; while (arcPos != NULL)//遍歷全部以頂點i爲弧尾的弧 { if (!(--indegree[arcPos->adjvex]))tmpS[++top] = arcPos->adjvex;//全部以頂點i爲直接前驅的頂點的入度減一 if (ve[i] + arcPos->weight > ve[arcPos->adjvex])ve[arcPos->adjvex] = ve[i] + arcPos->weight;//求全部以頂點i爲直接前驅的頂點的最先開始時間 /* 貌似與前面求事件最先發生時間ve(j)和最遲發生時間vl(j)不一樣,實則同樣, 由於在拓撲排序過程,會遍歷到全部的弧,每次記錄某個頂點(arcPos->adjvex)最先開始時間的,結束時存儲的就是最大的, */ arcPos = arcPos->nextarc; } } if (topoS->top < G->n - 1)return false;//有迴路 else return true; } bool criticalPath(adjGraph *G) { int *ve = (int*)malloc(sizeof(int)*G->n);//定義存儲頂點最先開始時間數組 int *vl = (int*)malloc(sizeof(int)*G->n);//定義存儲頂點最遲開始時間數組 if (!topologicalSort(G, ve))return false;//拓撲排序,並求出ve for (int i = 0; i < G->n; i++) { vl[i] = ve[G->n - 1]; }//初始化全部頂點的最遲開始時間,設爲最長路徑長度 int j = 0; while (topoS->top != -1) { j = topoS->verNum[topoS->top--];//你拓撲排序輸出 printf("\n%c\n", G->ver[j].data); arcNode *arcPos = G->ver[j].firstarc; while (arcPos != NULL)//遍歷全部以j爲弧尾的弧 { if (vl[arcPos->adjvex] - arcPos->weight < vl[j]) vl[j] = vl[arcPos->adjvex] - arcPos->weight;//記錄j的最遲開始時間 /* 貌似與前面求事件最先發生時間ve(j)和最遲發生時間vl(j)不一樣,實則同樣, 由於在拓撲排序過程,會遍歷到全部的弧,每次記錄某個頂點(j)最遲開始時間的,結束時存儲的就是最小的, */ arcPos = arcPos->nextarc; } } int ee = 0; int el = 0; for (int i = 0; i < G->n; i++) { arcNode *arcPos = G->ver[i].firstarc; while (arcPos != NULL) { ee = ve[i];//活動<i,arcPos->adjvex>的最先開始時間 el = vl[arcPos->adjvex] - arcPos->weight;//活動<i,arcPos->adjvex>的最遲開始時間 if (ee == el)printf("%c--%c:%d\n", G->ver[i].data, G->ver[arcPos->adjvex].data, arcPos->weight); arcPos = arcPos->nextarc; } } } #include<conio.h> int main(void) { freopen(".\\in.txt", "r", stdin); adjGraph *G = (adjGraph*)malloc(sizeof(adjGraph)); createAdjGraph(G); fclose(stdin); for (int i = 0; i < G->n; i++) { printf("%c->", G->ver[i].data); arcNode *pos = G->ver[i].firstarc; while (pos != NULL) { printf("%c->", G->ver[pos->adjvex].data); pos = pos->nextarc; } printf("\n"); }//輸出文本中存儲的圖鄰接表 if (!criticalPath(G)) printf("Error!There is a circuit!\n"); getch(); return 0; }
測試文本(記錄信息描述的就是本文第一個圖)
9,11
1 2 3 4 5 6 7 8 9
0,1,6
0,2,4
0,3,5
1,4,1
2,4,1
3,5,2
4,6,9
4,7,7
5,7,4
6,8,2
7,8,4
測試結果:
打印鄰接表
打印拓撲排序輸出和關鍵活動