第七章 圖
7.1 概念
- 連通圖:如果圖中任意兩點都有路徑,則該圖是連通圖
- 若一個有向圖恰有一個頂點的入度為0,其與定點入度為1,則是一顆有向樹
7.2 圖的物理存儲結構
因為圖的節點度數相差很大,按照度數最大的頂點設計節點結構會造成存儲單元浪費;如果按照每個頂點自己的度數設計不同結構,又會帶來操作的不便
一、鄰接矩陣
-
鄰接矩陣存儲使用2個數組存儲圖的信息:1個以為數組存儲頂點,一個二維數組存儲邊的信息
(1)二維數組中的對角線為0,以為不存在頂點到自身的邊
(2)要知道某個點的出度,就是頂點vi在第i行的元素之和,入度就是該頂點所在列的元素之和
(3)頂點vi的所有鄰接點就是吧矩陣中第i行元素掃描一遍
(4)對於有權值的網,二維數組中的元素不再是0,1表示是否存在邊,而是把元素值表示為權值。不存在的邊,權值記錄為\(\infty\);對角線上的權值為0.
-
鄰接矩陣定義圖
#include <stdio.h> typedef char VertexType; typedef int EdgeType; #define MAXVEX 100 #define IUNFINITY 65535 typedef struct { VertexType vexs[MAXVEX]; /* 頂點表*/ EdgeType arc[MAXVEX][MAXVEX]; /* 鄰接矩陣 */ int vnum,edgenum; /*定點的個數和邊的個數*/ }MGraphy; void createGraphy(MGraphy *g){ printf("input vetex num and edge num\n"); scanf("%d,%d",&g->vnum,&g->edgenum); for (int i = 0; i < g->vnum ; i++) { // 輸入頂點字符 printf("input %d vetex:",(i+1)); setbuf(stdin, NULL); scanf("%c",&g->vexs[i]); } for(int i=0;i<g->vnum;i++){ // 初始化數組元素 Infonity for(int j=0;j<g->vnum;j++){ g->arc[i][j] = IUNFINITY; } } printf("input a,b,c represent corner mark and weight\n"); for(int i=0;i<g->edgenum;i++){ int a,b,c=0; printf("%d edge:",(i+1)); setbuf(stdin,NULL); scanf("%d,%d,%d",&a,&b,&c); g->arc[a][b] = c; g->arc[b][a] = c; // 無向圖增加這個 } } int main() { MGraphy g ; createGraphy(&g); }
二. 鄰接表
-
鄰接矩陣對於頂點多而邊數少的稀疏圖造成存儲空間的大量浪費。正如線性表的預先分配可能造成存儲空間浪費,因此引入鏈式存儲結構。同樣可以考慮用鏈表存儲邊或弧。
-
鄰接表:數組 + 鏈表
(1)用的數組存儲每個節點
(2)數組中的每個節點的所有鄰接點組成一個鏈表(因為鄰接點的個數不確定)。這個鄰接表就是頂點的出度表
(3)鄰接表的圖形表示
(4)鄰接表關心了出度,但是查找入度就需要遍歷整個圖 -
創建鄰接表
#include <stdio.h> #include <malloc.h> typedef char VertexType; typedef int EdgeType; #define MAXVEX 100 #define IUNFINITY 65535 typedef struct EdgeNode{ int adjvex; /* 鄰接點域,該頂點對應的下標 */ EdgeType weight; EdgeNode *next; /* 鏈,指向下一個鄰接點 */ }EdgeNode; typedef struct VertexNode{ /* 頂點表結點 */ VertexType data; /* 節點名字 */ EdgeNode *firstedge; /* 邊表頭節點 */ }VertexNode; typedef struct{ VertexNode adjList[MAXVEX]; /* 頂點表是一個結構體數組,數組中的元素是Vertex節點 */ int vnum,enumber; /* 圖中當前頂點和邊數 */ }GraphyAdjList; /* 建立鄰接表結構 */ void createGraphy(GraphyAdjList *g){ EdgeNode *e; printf("input vertexNum and edgeNum:\n"); setbuf(stdin,NULL); scanf("%d,%d",&g->vnum,&g->enumber); for (int i = 0; i < g->vnum; i++) { printf("int %d vertex",(i+1)); setbuf(stdin,NULL); scanf("%c",&g->adjList[i].data); g->adjList[i].firstedge = NULL; /* 將邊表設為空 */ } /* 建立邊表 */ for (int k = 0; k < g->enumber; k++) { printf("input edge serialize num (i,j):\n"); int i,j; setbuf(stdin,NULL); scanf("%d,%d",&i,&j); e = (EdgeNode *) malloc (sizeof(EdgeNode)); } }
7.3 圖的遍歷
一. 基本思路
-
圖的遍歷:從圖中某一個頂點出發遍歷途中其余頂點,每一個頂點僅被訪問一次
-
基本思路
(1)樹有四種遍歷方式,因為根節點只有一個。而圖的復雜情況是的順着一個點向下尋找,極有可能最后又找到自己,形成回路導致死循環。
(2)所以要設置一個數組voisited[n],n是圖中頂點個數,初值為0,當該頂點被遍歷后,修改數組元素的值為1
(3)基於此,形成了2中遍歷方案:深度優先遍歷和廣度優先遍歷
二. 深度優先遍歷(DFS)
-
如下圖所示,我們進行深度遍歷,一個原則就是,每當我們發現有多個出度時,選擇右手邊的出度作為下一個遍歷的頂點路徑。
(1)從A出發,發現出度為B,F。選擇右手邊的B。A->B
(2)從B出發,出度為C,I,G,選擇右手邊的C
(3)從C出發,出度為I,D,選擇右手邊的D
(4)從D出發,出度為I,G,H,E,選擇右手邊的E
(5)從E出發,出度為H,F,選擇右手邊的F
(6)從F出發,出度為A,G,選擇右手邊的A,但發現A已經被遍歷過,所以選擇G
(7)從G出發,出度為B,D,H,B,D訪問過了,選擇H
(8)從H出發,出度為D,F,均被訪問過了。但此時圖中的節點並沒有遍歷完全,因此我們要按原路返回,去找沒走過的路
(9)回退到G,發現所連接的BDFH均被訪問;
(10)回退到F,沒有通道;回退到E,沒有通道,回退到D,發現一個點I,進行標記(若此時與D相鄰的還有其他頂點,則在此時一起進行標記);然后繼續回退到A,走完整個路 -
鄰接矩陣下的深度遍歷
int visited[MAXVEX] = {0}; void DFS(MGraphy g,int i){ visited[i] = 1; printf("%c,\t",g.vexs[i]); for (int j = 0; j < g.vnum; j++) { if(g.arc[i][j]!=0 && g.arc[i][j]!=IUNFINITY && !visited[j]){ DFS(g,j); } } } void DFSTraverse(MGraphy g){ printf("deep first search begin.\n"); for (int i = 0; i < g.vnum; i++) { if(!visited[i]){ DFS(g,i); } } } int main() { MGraphy g ; createGraphy(&g); printf("graphy create success ! ! !\n"); DFSTraverse(g); }
-
鄰接表下的深度遍歷
int visited[MAXVEX] = {0}; void DFS(Graph g, int i){ printf("%c",g.vset[i].name); visited[i] = 1; EdgeNode *edgeNode = g.vset[i].firstedgeNode; while(edgeNode!=NULL){ if(!visited[edgeNode->index]) DFS(g,edgeNode->index); edgeNode = edgeNode->next; } } void DFStraverse(Graph g){ for (int i = 0; i < g.vNum; i++) { // 用於不同連通分量 if(!visited[i]) DFS(g,i); } } int main() { Graph g; createGraphy(&g); printf("create graphy success ! ! !\n"); DFStraverse(g); }
三. 廣度優先遍歷
-
廣度優先遍歷類似輸的層次遍歷
(1)先入隊列一個元素
(2)彈出隊列頂端的1個元素打印,並把它連接的頂點入隊
(3)重復以上過程,直到隊列為空 -
BFS的過程
-
BFS的實現
(1)鄰接矩陣的BFStypedef char VertexType; typedef int EdgeType; #define MAXVEX 100 #define IUNFINITY 65535 typedef struct { VertexType vexs[MAXVEX]; /* 頂點表*/ EdgeType arc[MAXVEX][MAXVEX]; /* 鄰接矩陣 */ int vnum,edgenum; /*定點的個數和邊的個數*/ }MGraphy; /** * 鄰接矩陣遍歷圖 * @param g */ void BFSTraverse(MGraphy g){ SeqQueue *queue; initQueue(queue); // 順序表實現的隊列,先初始化 int visited[] = {0}; // 初始化每個結點對應為未訪問 int a; for(int i=0;i<g.vnum;i++){ // 對每個結點進行深度遍歷 if(visited[i] == 0){ visited[i] = 1; printf("%c",g.vexs[i]); // 深度遍歷后對結點進行打印操作 enQueue(queue,i); // 將節點放到隊列中 while (queueLength(queue)){ deQueue(queue,&a); // 取出對頭元素,進行廣度遍歷 for (int j = 0; j < g.vnum; ++j) { if(g.arc[a][j] == 1 && visited[j]==0){ // 存在邊,且對應的店沒有方問過 visited[j] = 1; printf("%c",g.vexs[j]); enQueue(queue,j); // 遍歷后再入隊 } } } } } }
(2)鄰接表的BFS
```c
typedef char VertexType;
typedef int EdgeType;
#define MAXVEX 100
#define IUNFINITY 65535
typedef struct EdgeNode{
int adjvex; /* 鄰接點域,該頂點對應的下標 */
EdgeType weight;
EdgeNode *next; /* 鏈,指向下一個鄰接點 */
}EdgeNode;
typedef struct VertexNode{ /* 頂點表結點 */
VertexType data; /* 節點名字 */
EdgeNode *firstedge; /* 邊表頭節點 */
}VertexNode;
typedef struct{
VertexNode adjList[MAXVEX]; /* 頂點表是一個結構體數組,數組中的元素是Vertex節點 */
int vnum,enumber; /* 圖中當前頂點和邊數 */
}GraphyAdjList;
/**
* 廣度優先遍歷鄰接表
* @param g
*/
void BFSTraverse2(GraphyAdjList *g){
SeqQueue *queue;
initQueue(queue);
int a;
int visited[g->vnum] = {0};
for (int i = 0; i < g->vnum; ++i) {
if(visited[i] == 0){
visited[i] = 1;
printf("%c",g->adjList[i].data); // 打印定點
enQueue(queue,i);
while(queueLength(queue)!=0){
deQueue(queue,&a);
EdgeNode *p = g->adjList[i].firstedge; // 進入結點的鄰接表
while (p!=NULL){
if(visited[p->adjvex] != 0){
visited[p->adjvex] == 1;
printf("%c\n",g->adjList[p->adjvex].data);
enQueue(queue,p->adjvex);
}
p = p->next;
}
}
}
}
}
```
7.4 最小生成樹
- 應用場景
設想有9個村庄,這些村庄構成如下圖所示的地理位置,每個村庄的直線距離都不一樣。若要在每個村庄間架設網絡線纜,若要保證成本最小,則需要選擇一條能夠聯通9個村庄,且長度最小的路線
二. 最小生成樹
-
最小生成樹的概念
(1)一個帶權值的圖:網。所謂最小成本,就是用n-1條邊把n個頂點連接起來,且連接起來的權值最小。
(2)我們把構造聯通網的最小代價生成樹稱為最小生成樹
(3)普里姆算法和克魯斯卡爾算法 -
普里姆算法
如下圖,普利姆的最小生成樹過程為:用Vs存儲已經遍歷的點,用Es存儲已經遍歷的邊
(1)選擇D為起點,加入Vs,與D連接的邊中,權值最小的邊為5,連接的點為A,因此將A加入到Vs,路徑DA加入到Es。
(2)此時Vs中存在D和A。與DA連接的邊中,權值最小的為6,連接的點為F,因此F加入到Vs,邊DF加入到Es。
(3)此時Vs中存在DAF,與DAF連接的邊中最小權值為7,連接的點為B,因此B加入Vs,路徑AB加入Es
(4)重復以上過程,知道Vs中加入了所有的點#include <stdio.h> typedef char VertexType; typedef int EdgeType; #define MAXVEX 100 #define IUNFINITY 65535 typedef struct { VertexType vexs[MAXVEX]; /* 頂點表*/ EdgeType arc[MAXVEX][MAXVEX]; /* 鄰接矩陣 */ int vnum,edgenum; /*定點的個數和邊的個數*/ }MGraphy; /** * 普里母最小生成樹:鄰接表表示,時間復雜度為O(n方) * @param g */ void miniSpanTree_Prim(MGraphy *g){ int adjVetex[MAXVEX] = {0}; // 保存相關定點下標 int lowcost[MAXVEX]; // 保存相關頂點間的權值 lowcost[0] = 0; for (int i = 1; i < g->vnum; ++i) // 循環除下標為0外的全部結點 lowcost[i] = g->arc[0][i]; // 初始化lowcost數組,每一個元素的值為0點和給點邊的權值 for (int i = 1; i < g->vnum; ++i) { // 循環除下標為0外的全部結點 int min = IUNFINITY; // 初始化最小權值為無窮 int j=1,k=0; while(j<g->vnum){ if(lowcost[j] != 0 && lowcost[j]<min){ // lowcost[j]為0表示當前點與其他點的權值數組 min = lowcost[j]; k = j; // k為遍歷到的最小權值邊連接的點 } j++; } printf("(%d,%d)",adjVetex[k],k); // 打印當前頂點邊中權值最小的邊 lowcost[k] = 0; for (int j = 1; j < g->vnum; ++j) { if(lowcost[j] != 0 && g->arc[k][j] < lowcost[j]){ lowcost[j] = g->arc[k][j]; // 將較小邊的權值並入lowcast adjVetex[j] = k; } } } }
-
克魯斯卡爾算法
克魯斯卡爾算法從邊的集合中挑選權值最小的,加入到選擇的邊集合中。如果這條邊,予以選擇的邊構成了回路,則舍棄這條邊。
如下圖所示,克魯斯卡爾的方法為:
(1)選擇權值最小為7的邊V7-V4
(2)選擇權值最小為8的邊V2-V8
(3)選擇權值最小為10的邊V1-V0
(4)選擇權值最小為11的邊V0-V5
(5)選擇全職最小為12的邊V1-V8,但是發現V1和V8全部是已經訪問的點,所以會構成回路,舍棄
(6)選擇權值最小為16的邊V1-V6
(7)選擇權值最小為16的邊V3-V7
(8)。。。。/* 科魯斯卡爾最小生成樹的邊的結構體 */ typedef struct{ int begin; int end; int weight; }Edge; typedef char VertexType; typedef int EdgeType; #define MAXVEX 100 #define IUNFINITY 65535 typedef struct { VertexType vexs[MAXVEX]; /* 頂點表*/ EdgeType arc[MAXVEX][MAXVEX]; /* 鄰接矩陣 */ int vnum,edgenum; /*定點的個數和邊的個數*/ }MGraphy; /** * 查找連線頂點的尾部下標 */ int find(int *parement,int f){ while (parement[f] > 0) f= parement[f]; return f; } void miniSpan_Kruskal(MGraphy *g){ Edge edges[g->edgenum]; // 定義邊集數組 int parement[g->vnum] = {0}; // 定義一個數組,用來判斷是否形成回路 /** * 此處省略將鄰接矩陣g轉化為邊集數組edges,並按照權值由大到小排序的過程 */ for(int i=0;i<g->edgenum;i++){ int n = find(parement,edges[i].begin); int m = find(parement,edges[i].end); if(n!=m){ // n != m說明沒有形成環路 parement[n] = m; // 將此邊的為節點放入到下標為起點的parement數組中 printf("(%d,%d) %d",edges[i].begin,edges[i].end,edges[i].weight); } } }
7.5 最短路徑
一. 迪傑斯特拉
-
迪傑斯特拉算法
(1)迪傑斯特拉,計算的是一個點到其余所有點的最短路徑。
(2)它的基本思想:
如果點 i 到點 j 的最短路徑經過k,則ij路徑中,i到k的那一段一定是i到k的最短路徑。 -
查找方法:
(1)聲明2個一維數組:一個用來標識當前頂點是否已經找到最短路徑。另一個數組用來記錄v0到該點的最短路徑中,該點的前一個頂點是什么。
(2)比較:計算\(v_0\)到\(v_i\)的最短路徑時,比較\(v_0\)\(v_i\)與\(v_0\)\(v_k\)+\(v_k\)\(v_i\)的大小,而\(v_0v_k\)與\(v_kv_i\)的值是暫時得出的記錄在數組中的最短路徑。 -
算法實現:基於鄰接矩陣
#include "graphy/graphy.c" // 鄰接矩陣 #define MAXVEX 9 #define INFINITY 65535 typedef int Pathmatrix[MAXVEX]; //存儲最短路徑下標的數組 typedef int ShortPathTable[MAXVEX]; //存儲到各點最短路徑的權值和 /** * 迪傑斯特拉:求有向圖G的V[0]到其余各點的最短路徑及帶權長度 * @return */ void shortestPath_Dijkstra(MGraphy *g,int v0,Pathmatrix *p,ShortPathTable *sptable){ int final[MAXVEX] = {0}; *p = {0}; // 初始化最短路徑數組為0 for (int i = 0; i < g->vnum; ++i) (*sptable)[i] = g->arc[v0][i]; //初始化sptable:讓最短路徑為圖中v0和其余各頂點的權值 (*sptable)[v0] = 0; // sptable記錄v0到v0的權值為0 final[v0] = 1; // final數組,記錄以求得v0到v0的最短路徑 /* 每次循環求得v0到頂點v的最短路徑 */ for (int i = 0; i< g->vnum ; ++i) { int min = INFINITY; int k; for (int j = 0; j < g->vnum; ++j) { // 循環每個頂點 if(! final[j] && (*sptable)[j] < min){ k = j; // 這個k只是把j的作用域擴大出去,供后面計算a min = (*sptable)[j]; // 讓min=當前被遍歷頂點與v0點的邊的權值 } final[k] = 1; for (int w = 0; w < g->vnum ; ++w) { int a = min+g->arc[k][w]; // 上面讓k=j,所以a=(*sptable)[j] + g->arc[j][w]。也就是:比如計算a0到a2,就比較a0a1+a1a2 與鄰接矩陣中的a0a2邊的權值 if(! final[w] && a < (*sptable)[w]) { (*sptable)[w] = a; (*p)[w] = k; // 這個k就是:假設該等式角標與程序無關,計算 a[i][j] > a[i][k]+a[k][j],記錄i到j的最短路徑中,j前面的節點 } } } } }
二. 弗洛伊德算法
-
弗洛伊德與迪傑斯特拉的區別
(1)它們都是基於比較\(v_0\)\(v_i\)與\(v_0\)\(v_k\)+\(v_k\)\(v_i\)的大小的基本算法。
(2)弗洛伊德三次循環計算出了每個點個其他點的最短路徑,迪傑斯特拉算法用2次循環計算出了一個點到其他各點的最短路徑 。
(3)如果要計算出全部的點到其他點的最短路徑,他們都是\(O(n^2)\)typedef int Pathmatrix_Floyd[MAXVEX][MAXVEX]; //存儲最短路徑下標的數組 typedef int ShortPathTable_Floyd[MAXVEX][MAXVEX]; //存儲到各點最短路徑的權值和 void shortPath_Floyd(MGraphy *g,Pathmatrix_Floyd *p,ShortPathTable_Floyd *D){ for (int i = 0; i < ; ++i) { for (int j = 0; j < g->vnum; ++j) { (*D)[i][j] = g -> arc[i][j]; (*p)[i][j] = j; } } for (int i = 0; i < g->vnum; ++i) { for (int j = 0; j < g->vnum; ++j) { for (int k = 0; k < g->vnum; ++k) { if((*D)[j][k] > (*D)[j][i]+(*D)[i][k]){ (*D)[j][k] = (*D)[j][i]+(*D)[i][k]; (*p)[j][k] = (*p)[j][i]; } } } } }
7.6 拓撲排序
一. 拓撲排序的概念
- 拓撲排序是對AOV網輸出的一個序列
- AOV網(Active on Vertex Network):在一個表示工程的有向圖中,用頂點表示活動,用弧表示活動之間的優先關系。這樣的圖稱為活動的網。
二. 拓撲排序的算法
-
步驟:
從AOV網中選擇一個入度為0的頂點然后刪除此頂點,並刪除以此頂點為。重復此步驟,直到輸出全部頂點或AOV網中不存在入度為0的頂點為止。 -
拓撲排序中頂點的數據結構:
(1)前面求最小生成樹和最短路徑時,都是使用鄰接矩陣,但由於拓撲排序中,需要刪除頂點,所以使用鄰接表方便。
(2)因為拓撲排序中,需要刪除入度為0的頂點,所以在原先的頂點數據結構中,加入入度域in。使頂點接都變為
![image_1auud954h1rbs1cm014lb16l83ip.png-3.1kB][23]#include <malloc.h> #define MAXVEX 100 typedef struct EdgeNode{ /* 邊表 */ int adjvex; /* 頂點下標 */ int weight; /* 權值 */ struct EdgeNode *next; /* 邊表中的下一節點 */ }EdgeNode; typedef struct VertexNode{ /* 定點表 */ int in; int data; EdgeNode *firstEdge; }VertexNode,AdjList[MAXVEX]; typedef struct{ AdjList adjList; int numVertexes,numEdges; }graphAdjList,* GraphAdjList; /** * 拓撲排序 * @param gl :鏈表 * @return :若GL無回路,則輸出排序序列並返回1;若有回路則返回-1 */ int topologicalSort(GraphAdjList gl){ int *stack = (int *)malloc(gl->numVertexes * sizeof(int)); // stack用於存儲入度為0的節點 int top = 0; // stack棧頂指針 int count; // 加入到棧中節點個數 for (int i = 0; i < gl->numVertexes; ++i) if(gl->adjList[i].in == 0) stack[++top] = i; while(top!=0){ int gettop = stack[top--]; printf("%d -> ",gl->adjList[gettop].data); count ++; for(EdgeNode *e=gl->adjList[gettop].firstEdge; e ; e=e->next){ int k = e->adjvex; // 頂點的下標 if( ! (-- gl->adjList[k].in)) // 將k號頂點入度減1 stack[++top] = k; // 如果發現入度為0,則把該頂點加入到棧中 } } int res = (count < gl->numVertexes) ? -1 : 1; // 如果最后遍歷到的個數小於圖的總定點數,則說明有環存在,返回-1 return res; }
7.7 關鍵路徑
一. 概念
-
拓撲排序是解決一個工程能否順序進行的問題,
-
當需要計算一個工程完成的最短時間,就需要用關鍵路徑。
-
拓撲排序使用的是AOV網(定點表示活動)。關鍵路徑使用AOE網(邊表示活動)。AOV網只能表示活動之間的制約關系,而AOE網可以用變得權值表示活動的持續時間。所以AOE網是建立在活動之間制約關系沒有矛盾的基礎上,再來分析整個工程需要多少時間。
-
路徑長度:路徑上各個活動持續的時間之和
關鍵路徑:從源點到匯點具有的最大長度的路徑
關鍵活動:關鍵路徑上的活動
二. 關鍵路徑算法
-
關鍵路徑算法中需要的變量:
(1)事件最早開始時間etv(earlist time of vertex):頂點\(v_k\)的最早發生時間
(2)事件最晚開始時間ltv(latest time of vertex) :頂點\(v_k\)的最晚發生時間,超過此時間,會延誤整個工期
(3)活動最早開始時間(earlist time of edge):弧\(a_k\)的最早發生時間
(4)活動最晚開始時間(latest time of edge) :弧\(a_k\)的最晚發生時間,就是不推遲工期的最晚開始時間int *etv,*ltv; /* 事件最早,最晚發生時間 */ int *stack2; /* 用於存儲拓撲排序的棧 */ int top2 = 0; /* stack2的棧頂指針 */ int topologicalSort2(GraphAdjList gl){ int *stack = (int *)malloc(gl->numVertexes * sizeof(int)); /* 建棧將入度為0的頂點入棧 */ int top = 0; for (int i = 0; i < gl->numVertexes; ++i) { if(0 == gl->adjList[i].in) stack[++top] = i; } etv = (int *)malloc(gl->numVertexes * sizeof(int)); /* 時間最早開時間數組 */ for (int i = 0; i < gl->numVertexes; ++i) /* 初始化最早開始時間數組全0 */ etv[i] = 0; int count = 0; stack2 = (int *)malloc(gl->numVertexes * sizeof(int)); while (top !=0 ){ int gettop = stack[top--]; count ++; stack2[++top2] = gettop; /* 將彈出的頂點序號壓入拓撲排序的棧 */ for (EdgeNode *e = gl->adjList[gettop].firstEdge; e ; e = e->next) { int k = e->adjvex; if( !(-- gl->adjList[k].in) ) stack[++top] = k; if( (etv[gettop] + e->weight) > etv[k] ) /* 求各點事件最早發生時間值 */ etv[k] = etv[gettop] + e->weight; } } if(count < gl->numVertexes) return -1; else return 1; } void criticalPath(GraphAdjList gl){ topologicalSort2(gl); ltv = (int *) malloc (gl->numVertexes * sizeof(int)); /* 事件最晚發生時間 */ for (int i = 0; i < gl->numVertexes; ++i) ltv[i] = etv[gl->numVertexes -1]; /* 初始化ltv */ int k; while(top2 != 0){ int gettop = stack2[top2--]; for(EdgeNode *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; } } for (int j = 0; j < gl->numVertexes; ++j) { for (EdgeNode *e = gl->adjList[j].firstEdge; e ; e = e->next) { k = e->adjvex; int ete = etv[j]; /* 活動最早發生時間 */ int lte = ltv[k] - e->weight; /* 活動最遲發生時間 */ if(ete == lte) printf("<v_%d , v_%d> length: %d",gl->adjList[j].data,gl->adjList[k].data,e->weight); } } }