https://cloud.tencent.com/developer/article/1012420
為了能講明白弗洛伊德(Floyd)算法的主要思想,我們先來看最簡單的案例。圖7-7-12的左圖是一個簡單的3個頂點的連通網圖。
我們先定義兩個二維數組D[3][3]和P[3][3], D代表頂點與頂點的最短路徑權值和的矩陣。P代表對應頂點的最短路徑的前驅矩陣。在未分析任何頂點之前,我們將D命名為D(-1),其實它就是初始圖的鄰接矩陣。將P命名為P(-1), 初始化為圖中的矩陣。
首先我們來分析,所有的頂點經過v0后到達另一頂點的最短路徑。因為只有3個頂點,因此需要查看v1->v0->v2,得到
D(-1)[1][0] + D(-1)[0][2] = 3。D(-1)[1][2]表示的是v1->v2的權值為5,我們發現D(-1)[1][2] > D(-1)[1][0] + D(-1)[0][2] ,通俗話來說就是
v1->v0->v2 比v1->v2距離還要近。所以我們就讓 D(-1)[1][2] = D(-1)[1][0] + D(-1)[0][2] = 3, 同樣地D(-1)[2][1] = 3, 於是就有了D(0)矩陣。因為有變化,所以P矩陣對應的P(-1)[1][2]和P(-1)[2][1]也修改為當前中轉的頂點v0的下標0, 於是就有了P(0)。也就是說
接下來,也就是在D(0)和P(0)的基礎上繼續處理所有頂點經過v1和v2后到達另一頂點的最短路徑,得到D(1)和P(1)、D(2)和P(2)完成所有頂點到所有頂點的最短路徑計算工作。
首先我們針對圖7-7-13的左網圖准備兩個矩陣D(-1)和P(-1),D(-1)就是網圖的鄰接矩陣,P(-1)初設為P[i][j]=j 這樣的矩陣。主要用來存儲路徑。
代碼如下(改編自《大話數據結構》):注意因為是要求所有頂點到所有頂點的最短路徑,因為使用二維數組。
#include<iostream> using namespace std; #define MAXEDGE 20 #define MAXVEX 20 #define INFINITY 65535 typedef struct { int vexs[MAXVEX]; int arc[MAXVEX][MAXVEX]; int numVertexes, numEdges; } MGraph; typedef int Patharc[MAXVEX][MAXVEX]; typedef int ShortPathTable[MAXVEX][MAXVEX]; /* 構建圖 */ void CreateMGraph(MGraph *G) { int i, j; /* printf("請輸入邊數和頂點數:"); */ G->numEdges = 16; G->numVertexes = 9; 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] = G->arc[j][i] = INFINITY; } } G->arc[0][1] = 1; G->arc[0][2] = 5; G->arc[1][2] = 3; G->arc[1][3] = 7; G->arc[1][4] = 5; G->arc[2][4] = 1; G->arc[2][5] = 7; G->arc[3][4] = 2; G->arc[3][6] = 3; G->arc[4][5] = 3; G->arc[4][6] = 6; G->arc[4][7] = 9; G->arc[5][7] = 5; G->arc[6][7] = 2; G->arc[6][8] = 7; G->arc[7][8] = 4; for(i = 0; i < G->numVertexes; i++) { for(j = i; j < G->numVertexes; j++) { G->arc[j][i] = G->arc[i][j]; } } } /* Floyd算法,求網圖G中各頂點v到其余頂點w的最短路徑P[v][w]及帶權長度D[v][w]。 */ void ShortestPath_Floyd(MGraph MG, Patharc P, ShortPathTable D) { int v, w, k; for (v = 0; v < MG.numVertexes; v++)/* 初始化D與P */ { for (w = 0; w < MG.numVertexes; w++) { D[v][w] = MG.arc[v][w];/* D[v][w]值即為對應點間的權值 */ P[v][w] = w;/* 初始化P */ } } for (k = 0; k < MG.numVertexes; k++) { for (v = 0; v < MG.numVertexes; v++) { for (w = 0; w < MG.numVertexes; w++) { /* 如果經過下標為k頂點路徑比原兩點間路徑更短 */ if (D[v][w] > D[v][k] + D[k][w]) { /* 將當前兩點間權值設為更小的一個 */ D[v][w] = D[v][k] + D[k][w]; P[v][w] = P[v][k];/* 路徑設置為經過下標為k的頂點 */ } } } } } int main(void) { int v, w, k; MGraph MG; Patharc P; ShortPathTable D; CreateMGraph(&MG); ShortestPath_Floyd(MG, P, D); cout << "各頂點間最短路徑如下: " << endl; for (v = 0; v < MG.numVertexes; v++) { for (w = v + 1; w < MG.numVertexes; w++) { cout << "v" << v << "--" << "v" << w << " weight: " << D[v][w] << " Path: " << v << ' '; k = P[v][w]; while (k != w) { cout << "-> " << k << " "; k = P[k][w]; } cout << "-> " << w << endl; } cout << endl; } return 0; }
輸出為:
程序中的算法代碼非常簡潔,即用了一個三層循環,k代表的是中轉結點的下標,v代表起始結點,w代表結束終點。k = 0 ~ 8,表示針對每個頂點作為中轉結點得到的計算結果,最終當k = 8時,兩矩陣數據如圖7-7-16所示。
從上圖我們可以看到第v2行的數值與Dijkstra算法求得的D數組的數值完全一樣,都是{4, 3, 0, 3, 1, 4, 6, 8, 12 }, 而且這里是所有頂點到所有頂點的最短路徑權值和都可以計算得出。那么如何由P這個路徑數組得出具體的最短路徑呢?以v2到v8為例,P[2][8] = 4,說明要經過頂點v4, 將4替換2,P[4][8] = 3, 說明經過v3, ......., 最終推導出最短路徑為:v2->v4->v3->v6->v7->v8。
Floyd算法使用了三層循環,故時間復雜度也為O(n^3),與Dijkstra算法一致,不過Floyd算法代碼簡潔,雖簡潔但也不一定好懂,還是需要多加揣摩才能領會。另外,雖然我們使用的例子都是無向圖的,但它們對於有向圖依然有效,只不過在創建圖的時候,有向圖的鄰接矩陣不是對稱的而已。