1. 拓撲排序
-
拓撲排序簡介
假設在如下面的一張 有向圖 中,頂點表示活動,弧表示活動之間的優先關係,這樣的 有向圖 頂點表示活動網,我們稱之爲 AOV 網。
上面 AOV 網的路徑爲:V1 -> V2 -> V3 -> V4 -> V5
或者 V1 -> V2 -> V3 -> V4 -> V5
設
G = (V,E)
是一個具有n個
頂點的 有向圖,V 中的頂點V1、V2...Vn
,若滿足從頂點Vi
到Vj
有一條路徑,則在頂點序列Vi必須在Vj
之前,則我們稱這樣的頂點序列爲 拓撲排序。所謂 拓撲排序,其實是對一個有向圖構造拓撲序列的過程。
構造過程拓撲序列會產生兩個結果:
1. 如果此網中的全部頂點被輸出,說明是不存在環(迴路)的AOV 網
2. 如果輸出頂點小於總頂點數,說明是存在環(迴路)的,不是AOV 網
-
拓撲排序實現
如上的一個AOV 網圖,
那麼AOV 網圖怎麼存儲比較合適呢?我們可以設計一個 鄰接表來存儲,鄰接表節點中增加一個變量,用來存儲頂點的入度,鄰接表節點結構如下:
頂點表結點
//頂點表結點
typedef struct VertexNode
{
//頂點入度
int in;
//頂點域,存儲頂點信息
int data;
//邊表頭指針
EdgeNode *firstedge;
}VertexNode, AdjList[MAXVEX];
圖結構:
//圖結構
typedef struct
{
AdjList adjList;
//圖中當前頂點數和邊數
int numVertexes,numEdges;
}graphAdjList,*GraphAdjList;
思路分析:
1. 利用棧的特性,先將圖網中所有入度爲 0 的頂點下標入棧,
2. 循環遍歷棧,直到棧爲空
3. 棧頂元素出棧,並遍歷與棧頂相連接的弧
3.1 獲取與棧頂元素對應頂點連接的頂點
3.2 將與棧頂元素對應頂點連接的頂點入度減一
3.3 判斷入度是否爲 0 ,爲 0 則入棧
先定義圖、鄰接矩陣和鄰接表
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXEDGE 20
#define MAXVEX 14
#define INFINITYC 65535
/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int Status;
// 鄰接矩陣
typedef struct {
int vexs[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGraph;
// 鄰接表結構
// 邊表節點
typedef struct EdgeNode{
int adjvex; // 鄰接頂點域,存儲頂點的下標
int weight; // 權值
struct EdgeNode *next; // 鏈域,存儲下一個鄰接點
}EdgeNode;
// 頂點表
typedef struct VertexNode{
int inCount; // 入度數
int data; // 頂點信息
EdgeNode *firstEdge; // 邊表頭指針
}VertexNode, AdjList[MAXVEX];
// 圖結構
typedef struct {
AdjList adjList;
int numVertexes, numEdges;
}graphAdiList, *GraphAdjList;
由於一個一個的輸入圖的頂點和邊,比較麻煩,我們定義一個函數,來構建鄰接矩陣的圖,然後轉換爲鄰接表
// 1.構成AOV網圖
void CreateMGraph(MGraph *G)/* 構件圖 */
{
int i, j;
G->numVertexes = MAXVEX;
G->numEdges = MAXEDGE;
for (i = 0; i < G->numVertexes; i++){
G->vexs[i] = i;
}
// 初始化圖
for (i = 0; i < G->numVertexes; i++){
for (j = 0; j < G->numVertexes; j++){
G->arc[i][j] = 0;
}
}
G->arc[0][4] =1;
G->arc[0][5] =1;
G->arc[0][11]=1;
G->arc[1][2] =1;
G->arc[1][4] =1;
G->arc[1][8] =1;
G->arc[2][5] =1;
G->arc[2][6] =1;
G->arc[2][9] =1;
G->arc[3][2] =1;
G->arc[3][13]=1;
G->arc[4][7] =1;
G->arc[5][8] =1;
G->arc[5][12]=1;
G->arc[6][5] =1;
G->arc[8][7] =1;
G->arc[9][10]=1;
G->arc[9][11]=1;
G->arc[10][13]=1;
G->arc[12][9] =1;
}
// 2.將AOV網圖藉助鄰近矩陣轉換成鄰接表結構
void CreateALGraph(MGraph G,GraphAdjList *GL)
{
int i,j;
EdgeNode *e;
// 創建圖
(*GL) = (GraphAdjList)malloc(sizeof(graphAdiList));
// 對邊和頂點賦值
(*GL)->numEdges = G.numEdges;
(*GL)->numVertexes = G.numVertexes;
// 讀入頂點表
for (i = 0; i < G.numVertexes; i++) {
(*GL)->adjList[i].inCount = 0;
(*GL)->adjList[i].data = G.vexs[i];
(*GL)->adjList[i].firstEdge = NULL;
}
for (i = 0; i < G.numVertexes; i++) {
for (j = 0; j < G.numVertexes; j++) {
if (G.arc[i][j]) {
// 創建邊表節點
e = (EdgeNode *)malloc(sizeof(EdgeNode));
//鄰接序號爲j
e->adjvex = j;
// 將當前頂點上的指向的結點指針賦值給e
e->next = (*GL)->adjList[i].firstEdge;
// 將當前頂點的指針指向e
(*GL)->adjList[i].firstEdge = e;
// 入度加1
(*GL)->adjList[j].inCount++;
}
}
}
}
接下來就是關鍵的拓撲排序
// 若AOV網圖無迴路則輸出拓撲排序的序列並且返回狀態值1,若存在迴路則返回狀態值0// 拓撲排序:解決的是一個工程能否順序進行的問題
Status TopologicalSort(GraphAdjList GL){
EdgeNode *e;
int i, k, getTop;
int top = 0; // 棧頂下標
int count = 0;// 統計頂點個數
// ✅ 創建棧
int *stack = (int *)malloc(sizeof(int) * GL->numVertexes);
// ✅ 遍歷入度爲0的頂點入棧
for (i = 0; i < GL->numVertexes; i++) {
if (GL->adjList[i].inCount == 0) {
stack[++top] = i;
}
}
printf("top = %d\n",top);
// ✅ 循環遍歷棧
while (top != 0) {
// ✅ 出棧棧頂元素
getTop = stack[top--];
printf("%d -> ",GL->adjList[getTop].data);
//輸出頂點,並計數
count++;
// ✅ 遍歷與棧頂相連接的弧
for (e = GL->adjList[getTop].firstEdge; e; e = e->next) {
// ✅ 獲取與gettop連接的頂點
k = e->adjvex;
// ✅ 將與gettop連接的頂點入度減1.判斷如果當前減1後爲0,則入棧
if (!(--GL->adjList[k].inCount)) {
//將k入棧到stack中,並且top加1;
stack[++top]=k;
}
}
}
// ✅ 判斷是否把所有的頂點都輸出. 則表示找到了拓撲排序;
if(count < GL->numVertexes)
return ERROR;
else
return OK;
}
2. 關鍵路徑
在一個表示工程的帶權有向圖中,用頂點表示事件,用有向邊表示活動,邊的權重表示活動的持續事件,這種有向圖的邊表表示活動的網,稱之爲AOE網。
沒有入邊的頂點稱之爲始點或源點;
沒有出邊的頂點稱之爲終點或匯點,由於一個工程,總有一個開始一個結束,所以正常情況下,AOE網只有一個源點和匯點。
比圖下面的AOE網
在AOE網中:
- 路徑上各個活動所持續的時間之和,稱爲路徑長度
- 從源點到匯點具有最大的路徑叫關鍵路徑
- 在關鍵路徑上的活動叫關鍵活動
那麼怎麼求解一個 AOE網 的關鍵路徑呢?接下來看一下,關鍵路徑求解過程中的幾個核心參數。
- 事件最早發生的時間
etv
:即頂點Vk的最早發生時間 - 事件最晚發生的時間
ltv
:即頂點Vk的最晚發生時間,也就是每個頂點對應的事件最晚需要開始的時間,超出此時間將會延誤整個工期 - 活動的最早開工時間**
ete
**:即弧Ak的最早發生時間 - 活動的最晚開工時間**
lte
**:即弧Ak的最晚發生時間(不推遲工期的最晚開工時間)
求事件的最早發生時間 etv
的過程,就是從頭到尾去找拓撲序列的過程,所以在求解關鍵路徑之前,我們需要調用一次拓撲排序的序列去計算etv
和拓撲序列列表
對上面的AOE網圖
的 etv
的求解分析 如下:
1. 從源點 V0->V1,a0 = 3,V0->V2,a1 = 4,所以etv[1] = 3,etv[2] = 4
2. V1->V3,a2 = 5,V2->V3,a4 = 8,所以etv[3] = max{3+5,4+8} = 12
由此可以推演出etv
的計算公式:
對上面的AOE網圖
的 ltv
的求解分析 如下:
首先將 ltv 初始化成 etv 最後一個事件的時間。(上圖最終求得etv[9] = 27)
1. 最後一個事件V9,沒有弧表(因爲是匯點),沒有要更新的相關 ltv
2. V8->V9 長度爲3,所以 ltv = min(27, 27-3) = 24,ltv[8] = 24
3. V7->V8 長度爲5,所以 ltv = min(24, 24-5) = 19,ltv[7] = 19
依次類推最終可以推演出ltv
的計算公式
代碼實現:
// 定義鄰接矩陣、鄰接表、圖
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXEDGE 30
#define MAXVEX 30
#define INFINITYC 65535
typedef int Status; /* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
// 鄰接矩陣
typedef struct {
int vexs[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGraph;
// 鄰接表
typedef struct EdgeNode{
int adjvex; // 鄰接點域,存儲頂點下標
int weight; // 權重
struct EdgeNode *next; // 鏈域,下一個鄰接點
}EdgeNode;
typedef struct VertexNode{
int in; // 入度
int data; // 頂點域,頂點信息
EdgeNode *firstEdge; // 邊表頭指針
}VertexNode, AdjverList[MAXVEX];
// 圖
typedef struct {
AdjverList adjList; // 頂點表
int numVertexes, numEdges;
}graphAdjList, *GraphAdjList;
// ✅ 通過鄰接矩陣將AOE網圖轉換爲鄰接表
void CreateMGraph(MGraph *G)/* 構件圖 */
{
int i, j;
// 頂點數和邊數
G->numEdges = 13;
G->numVertexes = 10;
// 初始化圖
for(i = 0; i < G->numVertexes; i++){
G->vexs[i] = i;
}
for(i = 0; i < G->numVertexes; i++){
for(j = 0; j < G->numVertexes; j++){
if(i == j){
G->arc[i][j] = 0;
}else {
G->arc[i][j]=INFINITYC;
}
}
}
G->arc[0][1]=3;
G->arc[0][2]=4;
G->arc[1][3]=5;
G->arc[1][4]=6;
G->arc[2][3]=8;
G->arc[2][5]=7;
G->arc[3][4]=3;
G->arc[4][6]=9;
G->arc[4][7]=4;
G->arc[5][7]=6;
G->arc[6][9]=2;
G->arc[7][8]=5;
G->arc[8][9]=3;
}
void CreateALGraph(MGraph G,GraphAdjList *GL){
int i, j;
EdgeNode *e;
*GL = (GraphAdjList)malloc(sizeof(graphAdjList));
(*GL)->numVertexes = G.numVertexes;
(*GL)->numEdges = G.numEdges;
// 讀取頂點表
for (i = 0; i < (*GL)->numVertexes; i++) {
(*GL)->adjList[i].in = 0;
(*GL)->adjList[i].data = G.vexs[i];
(*GL)->adjList[i].firstEdge = NULL;
}
// 讀取邊表
for (i = 0; i < (*GL)->numVertexes; i++) {
for (j = 0; j < (*GL)->numVertexes; j++) {
if (G.arc[i][j] != 0 && G.arc[i][j] < INFINITYC) {
e = (EdgeNode *)malloc(sizeof(EdgeNode));
e->adjvex = j;
e->weight = G.arc[i][j];
e->next = (*GL)->adjList[i].firstEdge;
(*GL)->adjList[i].firstEdge = e;
(*GL)->adjList[j].in++;
}
}
}
}
// ✅ 定義全局變量
int *etv,*ltv; /* 事件最早發生時間和最遲發生時間數組,全局變量 */
int *stack2; /* 用於存儲拓撲序列的棧 */
int top2; /* 用於stack2的指針*/
// ✅ 拓撲排序
Status TopologicalSort(GraphAdjList GL){
int i, k, getTop;
EdgeNode *e;
int top = 0;
int count = 0;
// 初始化棧
int *stack = (int *)malloc(sizeof(int) * GL->numVertexes);
// 入度爲0的頂點入棧
for (i = 0; i < GL->numVertexes; i++) {
if (GL->adjList[i].in == 0) {
stack[++top] = i;
}
}
// 創建初始化拓撲序列棧
top2 = 0;
stack2 = (int *)malloc(sizeof(int) * GL->numVertexes);
// 事件最早發生時間數組
etv = (int *)malloc(sizeof(int) * GL->numVertexes);
for (i = 0 ; i < GL->numVertexes; i++) {
//初始化
etv[i] = 0;
}
while (top != 0) {
// 出棧
getTop = stack[top--];
// 打印出棧頂點
printf("%d -> ", GL->adjList[getTop].data);
// 計數+1
count++;
// 將出棧的壓入拓撲序列棧
stack2[++top2] = getTop;
for (e = GL->adjList[getTop].firstEdge; e; e = e->next) {
k = e->adjvex;
if (!(--GL->adjList[k].in)) {
stack[++top] = k;
}
//求各頂點事件的最早發生的時間etv值
if ((etv[getTop] + e->weight) > etv[k]) {
etv[k] = etv[getTop] + e->weight;
}
}
}
if(count < GL->numVertexes)
return ERROR;
else
return OK;
return OK;
}
// ✅ 求關鍵路徑, GL爲有向網,則輸出G的各項關鍵活動;
void CriticalPath(GraphAdjList GL){
EdgeNode *e;
int i, j, k, getTop;
// 聲明活動最早和最晚發生時間
int ete, lte;
//求得拓撲序列,計算etv數組以及stack2的值
TopologicalSort(GL);
printf("etv:\n");
for(i = 0; i < GL->numVertexes; i++){
printf("etv[%d] = %d \n",i,etv[i]);
}
printf("\n");
//事件最晚發生時間數組
ltv = (int *)malloc(sizeof(int) * GL->numVertexes);
// 初始化ltv數組
for (i = 0; i < GL->numVertexes; i++) {
ltv[i] = etv[GL->numVertexes-1];
}
//計算ltv(事件最晚發生時間) 出棧求ltv
while (top2 != 0) {
// 出棧
getTop = stack2[top2--];
for (e = GL->adjList[getTop].firstEdge; e; e = e->next) {
k = e->adjvex;
if ((ltv[k] - e->weight) < ltv[getTop]) {
ltv[getTop] = ltv[k] - e->weight;
}
}
}
//打印ltv 數組
printf("ltv:\n");
for (i = 0 ; i < GL->numVertexes; i++) {
printf("ltv[%d] = %d \n",i,ltv[i]);
}
// 求解ete,lte 並且判斷lte與ete 是否相等.相等則是關鍵活動;
for (j = 0; j < GL->numVertexes; j++) {
for (e = GL->adjList[j].firstEdge; e; e = e->next) {
k = e->adjvex;
ete = etv[j];
lte = ltv[k] - e->weight;
if (ete == lte) {
printf("<%d-%d> length:%d\n",GL->adjList[j].data, GL->adjList[k].data, e->weight);
}
}
}
}