很不錯的一篇文章
上圖中有4個城市8條公路,公路上的數字表示這條公路的長短。請注意這些公路是單向的。我們現在需要求任意兩個城市之間的最短路程,也就是求任意兩個點之間的最短路徑。這個問題這也被稱爲“多源最短路徑”問題。
現在回到問題:如何求任意兩點之間最短路徑呢?通過之前的學習我們知道通過深度或廣度優先搜索可以求出兩點之間的最短路徑。所以進行n2遍深度或廣度優先搜索,即對每兩個點都進行一次深度或廣度優先搜索,便可以求得任意兩點之間的最短路徑。可是還有沒有別的方法呢?
1
2
3
4
5
6
7
8
|
for (i=1;i<=n;i++) { for (j=1;j<=n;j++) { if (
e[i][j] > e[i][1]+e[1][j] ) e[i][j]
= e[i][1]+e[1][j]; } } |
通過上圖我們發現:在只通過1號頂點中轉的情況下,3號頂點到2號頂點(e[3][2])、4號頂點到2號頂點(e[4][2])以及4號頂點到3號頂點(e[4][3])的路程都變短了。
接下來繼續求在只允許經過1和2號兩個頂點的情況下任意兩點之間的最短路程。如何做呢?我們需要在只允許經過1號頂點時任意兩點的最短路程的結果下,再判斷如果經過2號頂點是否可以使得i號頂點到j號頂點之間的路程變得更短。即判斷e[i][2]+e[2][j]是否比e[i][j]要小,代碼實現爲如下。
1
2
3
4
5
6
7
8
|
//經過1號頂點 for (i=1;i<=n;i++) for (j=1;j<=n;j++) if (e[i][j]
> e[i][1]+e[1][j]) e[i][j]=e[i][1]+e[1][j]; //經過2號頂點 for (i=1;i<=n;i++) for (j=1;j<=n;j++) if (e[i][j]
> e[i][2]+e[2][j]) e[i][j]=e[i][2]+e[2][j]; |
整個算法過程雖然說起來很麻煩,但是代碼實現卻非常簡單,核心代碼只有五行:
1
2
3
4
5
|
for (k=1;k<=n;k++) for (i=1;i<=n;i++) for (j=1;j<=n;j++) if (e[i][j]>e[i][k]+e[k][j]) e[i][j]=e[i][k]+e[k][j]; |
這段代碼的基本思想就是:最開始只允許經過1號頂點進行中轉,接下來只允許經過1和2號頂點進行中轉……允許經過1~n號所有頂點進行中轉,求任意兩點之間的最短路程。用一句話概括就是:從i號頂點到j號頂點只經過前k號點的最短路程。其實這是一種“動態規劃”的思想,關於這個思想我們將在《啊哈!算法2——偉大思維閃耀時》在做詳細的討論。下面給出這個算法的完整代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#include
<stdio.h> int main() { int e[10][10],k,i,j,n,m,t1,t2,t3; int inf=99999999; //用inf(infinity的縮寫)存儲一個我們認爲的正無窮值 //讀入n和m,n表示頂點個數,m表示邊的條數 scanf ( "%d
%d" ,&n,&m); //初始化 for (i=1;i<=n;i++) for (j=1;j<=n;j++) if (i==j)
e[i][j]=0; else e[i][j]=inf; //讀入邊 for (i=1;i<=m;i++) { scanf ( "%d
%d %d" ,&t1,&t2,&t3); e[t1][t2]=t3; } //Floyd-Warshall算法核心語句 for (k=1;k<=n;k++) for (i=1;i<=n;i++) for (j=1;j<=n;j++) if (e[i][j]>e[i][k]+e[k][j]
) e[i][j]=e[i][k]+e[k][j]; //輸出最終的結果 for (i=1;i<=n;i++) { for (j=1;j<=n;j++) { printf ( "%10d" ,e[i][j]); } printf ( "\n" ); } return 0; } |
有一點需要注意的是:如何表示正無窮。我們通常將正無窮定義爲99999999,因爲這樣即使兩個正無窮相加,其和仍然不超過int類型的範圍(C語言int類型可以存儲的最大正整數是2147483647)。在實際應用中最好估計一下最短路徑的上限,只需要設置比它大一點既可以。例如有100條邊,每條邊不超過100的話,只需將正無窮設置爲10001即可。如果你認爲正無窮和其它值相加得到一個大於正無窮的數是不被允許的話,我們只需在比較的時候加兩個判斷條件就可以了,請注意下面代碼中帶有下劃線的語句。
1
2
3
4
5
6
|
//Floyd-Warshall算法核心語句 for (k=1;k<=n;k++) for (i=1;i<=n;i++) for (j=1;j<=n;j++) if (e[i][k]<inf
&& e[k][j]<inf && e[i][j]>e[i][k]+e[k][j]) e[i][j]=e[i][k]+e[k][j]; |
上面代碼的輸入數據樣式爲:
1
2
3
4
5
6
7
8
9
|
4
8 1
2 2 1
3 6 1
4 4 2
3 3 3
1 7 3
4 1 4
1 5 4
3 12 |
此算法由Robert W. Floyd(羅伯特·弗洛伊德)於1962年發表在“Communications of the ACM”上。同年Stephen Warshall(史蒂芬·沃舍爾)也獨立發表了這個算法。Robert W.Floyd這個牛人是朵奇葩,他原本在芝加哥大學讀的文學,但是因爲當時美國經濟不太景氣,找工作比較困難,無奈之下到西屋電氣公司當了一名計算機操作員,在IBM650機房值夜班,並由此開始了他的計算機生涯。此外他還和J.W.J. Williams(威廉姆斯)於1964年共同發明了著名的堆排序算法HEAPSORT。堆排序算法我們將在第七章學習。Robert W.Floyd在1978年獲得了圖靈獎。
【一週一算法】算法6:只有五行的Floyd最短路算法
http://bbs.ahalei.com/thread-4554-1-1.html
(出處: 啊哈磊_編程從這裏起步)