由於圖的節點度數相差很大,按照度數最大的頂點設計節點結構會形成存儲單元浪費;若是按照每一個頂點本身的度數設計不一樣結構,又會帶來操做的不便算法
1、鄰接矩陣數組
鄰接矩陣存儲使用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); }
二. 鄰接表spa
鄰接矩陣對於頂點多而邊數少的稀疏圖形成存儲空間的大量浪費。正如線性表的預先分配可能形成存儲空間浪費,所以引入鏈式存儲結構。一樣能夠考慮用鏈表存儲邊或弧。設計
鄰接表:數組 + 鏈表
(1)用的數組存儲每一個節點
(2)數組中的每一個節點的全部鄰接點組成一個鏈表(由於鄰接點的個數不肯定)。這個鄰接表就是頂點的出度表
(3)鄰接表的圖形表示
(4)鄰接表關心了出度,可是查找入度就須要遍歷整個圖3d
建立鄰接表指針
#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)); } }
一. 基本思路code
圖的遍歷:從圖中某一個頂點出發遍歷途中其他頂點,每個頂點僅被訪問一次blog
基本思路
(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)鄰接矩陣的BFS
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; /** * 鄰接矩陣遍歷圖 * @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; } } } } } ```
二. 最小生成樹
最小生成樹的概念
(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); } } }
迪傑斯特拉算法
(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]; } } } } }
步驟:
從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; }
拓撲排序是解決一個工程可否順序進行的問題,
當須要計算一個工程完成的最短期,就須要用關鍵路徑。
拓撲排序使用的是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); } } }