/* Program: all-pairs-shortest-path-v1.c (Report comments/bugs to chikh@yuntech.edu.tw) Function: 實作Floyd-Warshall演算法,以書附的shortestPath.dat檔案作為輸入 Notes: 1) Floyd-Warshall演算法為「動態規劃」(dynamic programming)法 2) 讀取資料所用程式碼取自書附的shortestPath.c但調整函式的原型(prototype)且避用全域變數 3) 部分參考 https://www.geeksforgeeks.org/floyd-warshall-algorithm-dp-16/ 與 https://www.programiz.com/dsa/floyd-warshall-algorithm */ #include #include /* for exit() */ #define MAX_V 100 /* 最大頂點數 */ #define Inf 1073741823 /* 此為2^30-1的大數字,為本程式假設的無限大值;可改用limits.h定義的INT_MAX */ void init(int A[][MAX_V], int Y[][MAX_V], int *N) /* 讀取輸入資料檔、設定相鄰矩陣,把相關資料結構作初始化 */ { FILE *fptr; int i, j; int weight; if ((fptr=fopen("shortestPath.dat","r")) == NULL) { /* 開啟資料檔 */ perror("shortestPath.dat"); exit(1); } fscanf(fptr,"%d",N); /* 讀取圖形節點數 */ for (i = 1; i <= *N; i++ ) for (j = 1; j <= *N; j++) { A[i][j] = (i==j)? 0: Inf; /* 初始化A[1..N][1..N]相鄰矩陣,所有元素均先預設為∞ */ Y[i][j] = (i==j)? i: -1; } while (fscanf(fptr,"%d %d %d",&i,&j,&weight) != EOF) { A[i][j] = weight; /* 讀取連接頂點i與j的邊的權重值 */ Y[i][j] = i; /* Y[i][j]預設為i */ } fclose(fptr); } /* 底下floydWarshall()函式就每二頂點(i,j)之間計算最短距離值,並記錄可經由哪些中繼頂點達到最短 */ void floydWarshall(int A[][MAX_V], int Y[][MAX_V], int N) { int i, j, k; for (k = 1; k <= N; k++) for (i = 1; i < N; i++) for (j = 1; j <= N; j++) if (A[i][k] + A[k][j] < A[i][j]) { //if (A[i][k] != Inf && A[k][j] != Inf && A[i][k] + A[k][j] < A[i][j]) { A[i][j] = A[i][k] + A[k][j]; Y[i][j] = Y[k][j]; /* 不寫為 Y[i][j] = k */ } /* 上面二層for迴圈用於修正每二頂點(i,j)之間的距離值,檢視有否經由k(任一個可能的中繼點)能得到更小距離值 */ /* 留意line 53的寫法,不寫為 Y[i][j] = k,同學可區分有何差別嗎?! 理由:由line 37可知,若二頂點(i,j)之間有邊存在,將把Y[i][j]預設為i。 因此,在line 53中,若k與j有邊存在,寫為Y[k][j]與寫為k的意義相同,意為到j之前的中繼點為k。 但若k與j之間無直接的邊連接,則設Y[i][j] = Y[k][j],意為i到j的最後一個中繼點將由從k到j的路徑所記錄的最後 一個中繼點決定(Y[k][j]紀錄目前所知k到j距離j最近的中繼點)。 透過line 53設置Y矩陣內容,將可回溯得到最短路徑 */ } void printMatrix(int A[][MAX_V], int N) { int i, j; printf("\n\n運算後得頂點(i,j)間之矩陣資訊如下:\n "); for (i = 1; i <= N; i++) printf("%4d",i); for (i = 0; i <= N; i++) printf("%4s",i==0? "\n +":"----"); for (i = 1; i <= N; i++) { printf("\n頂點%d |",i); for (j = 1; j <= N; j++) { if (A[i][j] == Inf) printf("%5s","∞"); /* somewhat weird to use %5s instead of %4s here */ else printf("%4d",A[i][j]); } //printf("\n"); } } int backtrack(int A[][MAX_V], int Y[][MAX_V], int source, int dest) { int distance, k = Y[source][dest]; /* k為source到dest之間的中繼頂點 */ if (source == dest) { printf("頂點%d →",source); return A[source][dest]; } if (Y[source][dest] == -1) { printf("頂點%d與%d之間沒有路徑存在:( ",source,dest); return Inf; } distance = backtrack(A,Y,source,k); printf(" %d →",dest); return distance+A[k][dest]; } void traverseMatrix(int A[][MAX_V],int Y[][MAX_V]) { int distance, source, sink; printf("\n\n查詢任二點之間的最短路徑\n源點(0退出查詢) => "); scanf("%d",&source); if (source == 0) return; printf("終點(0退出查詢) => "); scanf("%d",&sink); if (sink == 0) return; printf("最短路徑:"); distance = backtrack(A,Y,source,sink); printf("\b\b (所經之邊距離總和=%d)(查表A[%d][%d]=%d)",distance,source,sink,A[source][sink]); /* 抹除最後顯示多餘的"→" */ } int main() { int N, A[MAX_V][MAX_V], Y[MAX_V][MAX_V] = {0}; /* N為圖所含的頂點實際總數;A[1..N][1..N]為圖形的相鄰矩陣 */ /* Y[1..N][1..N]記錄任二頂點(i,j)之間距j最近的中繼點,將用於最短路徑回溯之用 */ printf("\n*** 解 all-pairs shortest-path 問題 ***"); init(A,Y,&N); /* 讀取輸入資料檔、設定相鄰矩陣,並把相關資料結構作初始化 */ floydWarshall(A,Y,N); printMatrix(A,N); //printMatrix(Y,N); traverseMatrix(A,Y); return 0; }