1、單源點的最短路徑問題:給定帶權有向圖G和源點v,求從v到G中其余各頂點的最短路徑。
我們用一個例子來具體說明迪傑斯特拉算法的流程。
定義源點為 0,dist[i]
為源點 0 到頂點 i 的最短路徑。其過程描述如下:
步驟 | dist[1] | dist[2] | dist[3] | dist[4] | 已找到的集合 |
---|---|---|---|---|---|
第 1 步 | 8 | 1 | 2 | +∞ | {2} |
第 2 步 | 8 | × | 2 | 4 | {2, 3} |
第 3 步 | 5 | × | × | 4 | {2, 3, 4} |
第 4 步 | 5 | × | × | × | {2, 3, 4, 1} |
第 5 步 | × | × | × | × | {2, 3, 4, 1} |
第 1 步:從源點 0 開始,找到與其鄰接的點:1,2,3,更新dist[]
數組,因 0 不與 4 鄰接,故dist[4]
為正無窮。在dist[]
中找到最小值,其頂點為 2,即此時已找到 0 到 2 的最短路。
第 2 步:從 2 開始,繼續更新dist[]
數組:2 與 1 不鄰接,不更新;2 與 3 鄰接,因0→2→3
比dist[3]
大,故不更新dist[3]
;2 與 4 鄰接,因0→2→4
比dist[4]
小,故更新dist[4]
為 4。在dist[]
中找到最小值,其頂點為 3,即此時又找到 0 到 3 的最短路。
第 3 步:從 3 開始,繼續更新dist[]
數組:3 與 1 鄰接,因0→3→1
比dist[1]
小,更新dist[1]
為 5;3 與 4 鄰接,因0→3→4
比dist[4]
大,故不更新。在dist[]
中找到最小值,其頂點為 4,即此時又找到 0 到 4 的最短路。
第 4 步:從 4 開始,繼續更新dist[]
數組:4 與 1 不鄰接,不更新。在dist[]
中找到最小值,其頂點為 1,即此時又找到 0 到 1 的最短路。
第 5 步:所有點都已找到,停止。
對於上述步驟,你可能存在以下的疑問:
若 A 作為源點,與其鄰接的只有 B,C,D 三點,其dist[]
最小時頂點為 C,即就可以確定A→C
為 A 到 C 的最短路。但是我們存在疑問的是:是否還存在另一條路徑使 A 到 C 的距離更小? 用反證法證明。
假設存在如上圖的紅色虛線路徑,使A→D→C
的距離更小,那么A→D
作為A→D→C
的子路徑,其距離也比A→C
小,這與前面所述 “dist[]
最小時頂點為 C” 矛盾,故假設不成立。因此這個疑問不存在。
根據上面的證明,我們可以推斷出,Dijkstra 每次循環都可以確定一個頂點的最短路徑,故程序需要循環 n-1 次。
1 /* 2 迪傑斯特拉求單節點到其余各節點的最短路徑。 3 visited數組用於保存頂點是否已經求過最短路徑,pre數組用於保存最短路徑的下標 4 dist數組用於保存初始節點到其余節點的最短路徑長度。 5 該算法求有向圖G的某頂點到其余節點的最短路徑pre以及長度dist 6 pre[v]的值是v0-->...->v的路徑中的前驅節點。D[v]表示v0-->...-->v的最短路徑長度和。 7 8 可以證明迪傑斯特拉算法每次循環可以確定一個頂點的最短路徑,所以主程序循環n-1次。 9 主程序循環主要做兩件事:首先找出dist數組中的最小值,並記錄下標,說明找到初始點到該下標的最短路徑。 10 然后要比價初始點到該點的最短路徑加上這點到其他初始點需要到的點的距離是否比初始點直接到這些點的距離短 11 如果要短,那么就更新dist數組,並且這些點的前驅節點就會變為v而不是開始的v0點。下一次主循環再去從dist中找 12 最小的值並且未求過的點,就是該點的最短路徑。 13 */ 14 #include<iostream> 15 using namespace std; 16 int matrix[100][100];//鄰接矩陣 17 bool visited[100];//標記數組 18 int dist[100];//原點到i頂點的最短距離 19 int pre[100];//記錄最短路徑。pre[i]放的是i的前驅節點 20 int source;//源節點 21 int vertex_num;//頂點數 22 int edge_num;//邊數 23 24 void Dijkstra(int source) 25 { 26 //首先初始化 27 memset(visited,0,sizeof(visited)); 28 visited[source] = true; 29 for (int i = 0; i < vertex_num; i++) 30 { 31 dist[i] = matrix[source][i]; 32 pre[i] = source; 33 } 34 35 int min_cost;//最短距離 36 int min_cost_index;//權值最小的那個頂點的下標。(求好了) 37 //主循環 38 for (int i = 1; i < vertex_num; i++) 39 { 40 min_cost = INT_MAX; 41 for (int j = 0; j < vertex_num; j++) 42 { 43 //注意要確保這個點沒有找過。 44 if (visited[j]==false&&dist[j] < min_cost) 45 { 46 min_cost_index = j; 47 min_cost = dist[j]; 48 } 49 } 50 51 visited[min_cost_index] = true;//找到某一個點的最短距離 52 //利用該點進行dist的更新,並且調整前驅。 53 for (int j = 0; j < vertex_num; j++) 54 { 55 //確保有連接 56 if (visited[j] == false && matrix[min_cost_index][j] != INT_MAX&&min_cost+ matrix[min_cost_index][j] < dist[j]) 57 { 58 dist[j] = min_cost + matrix[min_cost_index][j]; 59 pre[j] = min_cost_index; 60 } 61 } 62 } 63 } 64 65 int main() 66 { 67 cout << "請輸入圖的頂點數(<100):"; 68 cin >> vertex_num; 69 cout << "請輸出圖的邊數: "; 70 cin >> edge_num; 71 for (int i = 0; i < vertex_num; i++) 72 { 73 for (int j = 0; j < vertex_num; j++) 74 { 75 matrix[i][j] = (i != j) ? INT_MAX : 0; 76 } 77 } 78 cout << "請輸入邊的信息:\n"; 79 int u, v, w; 80 for (int i = 0; i < edge_num; i++) 81 { 82 cin >> u >> v >> w; 83 matrix[u][v] = matrix[v][u] = w; 84 } 85 86 cout << "請輸入源點(<" << vertex_num << "): "; 87 cin >> source; 88 Dijkstra(source); 89 for (int i = 0; i < vertex_num; i++) 90 { 91 if (i != source) 92 { 93 //路徑是反的,從目標點向前不斷找前驅的過程。 94 cout << source << "到" << i << "最短距離: " << dist[i] << ",路徑是:" << i; 95 int t = pre[i]; 96 while (t != source) 97 { 98 cout << "--" << t; 99 t = pre[t]; 100 } 101 cout << "--" << source << endl; 102 } 103 } 104 return 0; 105 }
2、弗洛伊德算法:
1)算法思想原理:
Floyd算法是一個經典的動態規划算法。用通俗的語言來描述的話,首先我們的目標是尋找從點i到點j的最短路徑。從動態規划的角度看問題,我們需要為這個目標重新做一個詮釋(這個詮釋正是動態規划最富創造力的精華所在)
從任意節點i到任意節點j的最短路徑不外乎2種可能,1是直接從i到j,2是從i經過若干個節點k到j。所以,我們假設Dis(i,j)為節點u到節點v的最短路徑的距離,對於每一個節點k,我們檢查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,證明從i到k再到j的路徑比i直接到j的路徑短,我們便設置Dis(i,j) = Dis(i,k) + Dis(k,j),這樣一來,當我們遍歷完所有節點k,Dis(i,j)中記錄的便是i到j的最短路徑的距離。
2).算法描述:
a.從任意一條單邊路徑開始。所有兩點之間的距離是邊的權,如果兩點之間沒有邊相連,則權為無窮大。
b.對於每一對頂點 u 和 v,看看是否存在一個頂點 w 使得從 u 到 w 再到 v 比己知的路徑更短。如果是更新它。
3).Floyd算法過程矩陣的計算----十字交叉法
方法:兩條線,從左上角開始計算一直到右下角 如下所示
給出矩陣,其中矩陣A是鄰接矩陣,而矩陣Path記錄u,v兩點之間最短路徑所必須經過的點
相應計算方法如下:
最后A3即為所求結果
1 typedef struct 2 { 3 char vertex[VertexNum]; //頂點表 4 int edges[VertexNum][VertexNum]; //鄰接矩陣,可看做邊表 5 int n,e; //圖中當前的頂點數和邊數 6 }MGraph; 7 8 void Floyd(MGraph g) 9 { 10 int A[MAXV][MAXV]; 11 int path[MAXV][MAXV]; 12 int i,j,k,n=g.n; 13 for(i=0;i<n;i++) 14 for(j=0;j<n;j++) 15 { 16 A[i][j]=g.edges[i][j]; 17 path[i][j]=-1; 18 } 19 for(k=0;k<n;k++) 20 { 21 for(i=0;i<n;i++) 22 for(j=0;j<n;j++) 23 if(A[i][j]>(A[i][k]+A[k][j])) 24 { 25 A[i][j]=A[i][k]+A[k][j]; 26 path[i][j]=k; 27 } 28 } 29 }