暑假,小哼準備去一些城市旅遊。有些城市之間有公路,有些城市之間則沒有,以下圖。爲了節省經費以及方便計劃旅程,小哼但願在出發以前知道任意兩個城市以前的最短路程。
上圖中有4個城市8條公路,公路上的數字表示這條公路的長短。請注意這些公路是單向的。咱們如今須要求任意兩個城市之間的最短路程,也就是求任意兩個點之間的最短路徑。這個問題這也被稱爲「多源最短路徑」問題。
如今須要一個數據結構來存儲圖的信息,咱們仍然能夠用一個4*4的矩陣(二維數組e)來存儲。好比1號城市到2號城市的路程爲2,則設e[1][2]的值爲2。2號城市沒法到達4號城市,則設置e[2][4]的值爲∞。另外此處約定一個城市本身是到本身的也是0,例如e[1][1]爲0,具體以下。
如今回到問題:如何求任意兩點之間最短路徑呢?經過以前的學習咱們知道經過深度或廣度優先搜索能夠求出兩點之間的最短路徑。因此進行n2遍深度或廣度優先搜索,即對每兩個點都進行一次深度或廣度優先搜索,即可以求得任意兩點之間的最短路徑。但是還有沒有別的方法呢?
咱們來想想,根據咱們以往的經驗,若是要讓任意兩點(例如從頂點a點到頂點b)之間的路程變短,只能引入第三個點(頂點k),並經過這個頂點k中轉即a->k->b,纔可能縮短原來從頂點a點到頂點b的路程。那麼這個中轉的頂點k是1~n中的哪一個點呢?甚至有時候不僅經過一個點,而是通過兩個點或者更多點中轉會更短,即a->k1->k2b->或者a->k1->k2…->k->i…->b。好比上圖中從4號城市到3號城市(4->3)的路程e[4][3]本來是12。若是隻經過1號城市中轉(4->1->3),路程將縮短爲11(e[4][1]+e[1][3]=5+6=11)。其實1號城市到3號城市也能夠經過2號城市中轉,使得1號到3號城市的路程縮短爲5(e[1][2]+e[2][3]=2+3=5)。因此若是同時通過1號和2號兩個城市中轉的話,從4號城市到3號城市的路程會進一步縮短爲10。經過這個的例子,咱們發現每一個頂點都有可能使得另外兩個頂點之間的路程變短。好,下面咱們將這個問題通常化。
當任意兩點之間不容許通過第三個點時,這些城市之間最短路程就是初始路程,以下。
假如如今只容許通過1號頂點,求任意兩點之間的最短路程,應該如何求呢?只需判斷e[i][1]+e[1][j]是否比e[i][j]要小便可。e[i][j]表示的是從i號頂點到j號頂點之間的路程。e[i][1]+e[1][j]表示的是從i號頂點先到1號頂點,再從1號頂點到j號頂點的路程之和。其中i是1~n循環,j也是1~n循環,代碼實現以下。
1 for (i = 1; i <= n; i++)
2 {
3 for (j = 1; j <= n; j++)
4 {
5 if (e[i][j] > e[i][1] + e[1][j])
6 e[i][j] = e[i][1] + e[1][j];
7 }
8 }
在只容許通過1號頂點的狀況下,任意兩點之間的最短路程更新爲:
經過上圖咱們發現:在只經過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 //通過1號頂點
2 for(i=1;i<=n;i++)
3 for(j=1;j<=n;j++)
4 if (e[i][j] > e[i][1]+e[1][j]) e[i][j]=e[i][1]+e[1][j];
5 //通過2號頂點
6 for(i=1;i<=n;i++)
7 for(j=1;j<=n;j++)
8 if (e[i][j] > e[i][2]+e[2][j]) e[i][j]=e[i][2]+e[2][j];
在只容許通過1和2號頂點的狀況下,任意兩點之間的最短路程更新爲:
經過上圖得知,在相比只容許經過1號頂點進行中轉的狀況下,這裏容許經過1和2號頂點進行中轉,使得e[1][3]和e[4][3]的路程變得更短了。
同理,繼續在只容許通過一、2和3號頂點進行中轉的狀況下,求任意兩點之間的最短路程。任意兩點之間的最短路程更新爲:
最後容許經過全部頂點做爲中轉,任意兩點之間最終的最短路程爲:
整個算法過程雖說起來很麻煩,可是代碼實現卻很是簡單,核心代碼只有五行:
1 for(k=1;k<=n;k++)
2 for(i=1;i<=n;i++)
3 for(j=1;j<=n;j++)
4 if(e[i][j]>e[i][k]+e[k][j])
5 e[i][j]=e[i][k]+e[k][j];
這段代碼的基本思想就是:最開始只容許通過1號頂點進行中轉,接下來只容許通過1和2號頂點進行中轉……容許通過1~n號全部頂點進行中轉,求任意兩點之間的最短路程。用一句話歸納就是:從i號頂點到j號頂點只通過前k號點的最短路程。
1 #include
2 int main()
3 {
4 int e[10][10],k,i,j,n,m,t1,t2,t3;
5 int inf=99999999; //用inf(infinity的縮寫)存儲一個咱們認爲的正無窮值
6 //讀入n和m,n表示頂點個數,m表示邊的條數
7 scanf("%d %d",&n,&m);
8 //初始化
9 for(i=1;i<=n;i++)
10 for(j=1;j<=n;j++)
11 if(i==j) e[i][j]=0;
12 else e[i][j]=inf;
13 //讀入邊
14 for(i=1;i<=m;i++)
15 {
16 scanf("%d %d %d",&t1,&t2,&t3);
17 e[t1][t2]=t3;
18 }
19 //Floyd-Warshall算法核心語句
20 for(k=1;k<=n;k++)
21 for(i=1;i<=n;i++)
22 for(j=1;j<=n;j++)
23 if(e[i][j]>e[i][k]+e[k][j] )
24 e[i][j]=e[i][k]+e[k][j];
25 //輸出最終的結果
26 for(i=1;i<=n;i++)
27 {
28 for(j=1;j<=n;j++)
29 {
30 printf("%10d",e[i][j]);
31 }
32 printf("\n");
33 }
34 return 0;
35 }
另外須要注意的是:Floyd-Warshall算法不能解決帶有「負權迴路」(或者叫「負權環」)的圖,由於帶有「負權迴路」的圖沒有最短路。例以下面這個圖就不存在1號頂點到3號頂點的最短路徑。由於1->2->3->1->2->3->…->1->2->3這樣路徑中,每繞一次1->-2>3這樣的環,最短路就會減小1,永遠找不到最短路。其實若是一個圖中帶有「負權迴路」那麼這個圖則沒有最短路。