僅僅有五行的Floyd最短路算法

                     


暑假,小哼準備去一些城市旅遊。有些城市之間有公路,有些城市之間則沒有,例如如下圖。爲了節省經費以及方便計劃旅程,小哼但願在出發以前知道隨意兩個城市以前的最短路程。算法

081028xjgvimgz7882qdu7.png

上圖中有4個城市8條公路,公路上的數字表示這條公路的長短。請注意這些公路是單向的。vim

咱們現在需要求隨意兩個城市之間的最短路程,也就是求隨意兩個點之間的最短路徑。這個問題這也被稱爲「多源最短路徑」問題。數組

現在需要一個數據結構來存儲圖的信息,咱們仍然可以用一個4*4的矩陣(二維數組e)來存儲。比方1號城市到2號城市的路程爲2,則設e[1][2]的值爲2。2號城市沒法到達4號城市,則設置e[2][4]的值爲∞。數據結構

另外此處約定一個城市本身是到本身的也是0。好比e[1][1]爲0,詳細例如如下。ide

081028o2n5ebn8hdeh9e5l.png

現在回到問題:怎樣求隨意兩點之間最短路徑呢?經過以前的學習咱們知道經過深度或廣度優先搜索可以求出兩點之間的最短路徑。因此進行n2遍深度或廣度優先搜索,即對每兩個點都進行一次深度或廣度優先搜索,便可以求得隨意兩點之間的最短路徑。post

可是還有沒有別的方法呢?學習

咱們來想想,依據咱們以往的經驗。假設要讓隨意兩點(好比從頂點a點到頂點b)之間的路程變短。僅僅能引入第三個點(頂點k)。並經過這個頂點k中轉即a->k->b,纔可能縮短原來從頂點a點到頂點b的路程。那麼這個中轉的頂點k是1~n中的哪一個點呢?甚至有時候不只僅經過一個點,而是通過兩個點或者不少其它點中轉會更短。即a->k1->k2b->或者a->k1->k2…->k->i…->b。spa

比方上圖中從4號城市到3號城市(4->3)的路程e[4][3]本來是12。3d

假設僅僅經過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。經過這個的樣例,咱們發現每個頂點都有可能使得另外兩個頂點之間的路程變短。好,如下咱們將這個問題通常化。orm

當隨意兩點之間不一樣意通過第三個點時。這些城市之間最短路程就是初始路程,例如如下。

081029zdxxq919ttqt8tu8.png

如現在僅僅贊成通過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.     }   

在僅僅贊成通過1號頂點的狀況下,隨意兩點之間的最短路程更新爲:

081029itl7z7m4l9qqg56d.png

經過上圖咱們發現:在僅僅經過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號頂點的狀況下,隨意兩點之間的最短路程更新爲:

081029e7gjlaaul4zk7z4n.png

經過上圖得知,在相比僅僅贊成經過1號頂點進行中轉的狀況下。這裏贊成經過1和2號頂點進行中轉,使得e[1][3]和e[4][3]的路程變得更短了。

同理,繼續在僅僅贊成通過一、2和3號頂點進行中轉的狀況下,求隨意兩點之間的最短路程。

隨意兩點之間的最短路程更新爲:

081029pd747o8o87o07o7l.png

最後贊成經過所有頂點做爲中轉,隨意兩點之間終於的最短路程爲:

081030h7tmht7cs2h7qftu.png

整個算法過程儘管提及來很是麻煩,但是代碼實現卻很是easy,核心代碼僅僅有五行:

  
  
  
  
  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號點的最短路程。事實上這是一種「動態規劃」的思想,關於這個思想咱們將在《啊哈。算法2——偉大思惟閃耀時》在作具體的討論。

如下給出這個算法的完整代碼:

  
  
  
  
  1. #include <stdio.h>   
  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.     //初始化   
  10.     for(i=1;i<=n;i++)   
  11.  for(j=1;j<=n;j++)   
  12.      if(i==j) e[i][j]=0;   
  13. else e[i][j]=inf;   
  14.     //讀入邊   
  15.     for(i=1;i<=m;i++)   
  16.     {   
  17.  scanf("%d %d %d",&t1,&t2,&t3);   
  18.  e[t1][t2]=t3;   
  19.     }   
  20.   
  21.     //Floyd-Warshall算法核心語句   
  22.     for(k=1;k<=n;k++)   
  23.  for(i=1;i<=n;i++)   
  24.      for(j=1;j<=n;j++)   
  25.   if(e[i][j]>e[i][k]+e[k][j] )   
  26.       e[i][j]=e[i][k]+e[k][j];   
  27.   
  28.     //輸出終於的結果   
  29.     for(i=1;i<=n;i++)   
  30.     {   
  31.      for(j=1;j<=n;j++)   
  32.  {   
  33.      printf("%10d",e[i][j]);   
  34.  }   
  35.  printf("\n");   
  36.     }   
  37.   
  38.     return 0;   

有一點需要注意的是:怎樣表示正無窮。咱們一般將正無窮定義爲99999999,因爲這樣即便兩個正無窮相加,其和仍然不超過int類型的範圍(C語言int類型可以存儲的最大正整數是2147483647)。在實際應用中最好預計一下最短路徑的上限,僅僅需要設置比它大一點既可以。

好比有100條邊,每條邊不超過100的話。僅僅需將正無窮設置爲10001就能夠。假設你以爲正無窮和其餘值相加獲得一個大於正無窮的數是不被贊成的話,咱們僅僅需在比較的時候加兩個推斷條件就可以了,請注意如下代碼中帶有下劃線的語句。

  
  
  
  
  1. //Floyd-Warshall算法核心語句   
  2. for(k=1;k<=n;k++)   
  3.   for(i=1;i<=n;i++)   
  4.       for(j=1;j<=n;j++)   
  5.  if(e[i][k]<inf && e[k][j]<inf && e[i][j]>e[i][k]+e[k][j])   
  6.      e[i][j]=e[i][k]+e[k][j]; 

上面代碼的輸入數據樣式爲:

  
  
  
  
  1. 4 8   
  2. 1 2 2   
  3. 1 3 6   
  4. 1 4 4   
  5. 2 3 3   
  6. 3 1 7   
  7. 3 4 1   
  8. 4 1 5   
  9. 4 3 12 

第一行兩個數爲n和m,n表示頂點個數,m表示邊的條數。

接下來m行。每一行有三個數t一、t2 和t3,表示頂點t1到頂點t2的路程是t3。

獲得終於結果例如如下:

081030is22w3mmnz3r33m3.png

經過這樣的方法咱們可以求出隨意兩個點之間最短路徑。它的時間複雜度是O(N3)。使人很震撼的是它竟然僅僅有五行代碼,實現起來很easy。正是因爲它實現起來很easy,假設時間複雜度要求不高。使用Floyd-Warshall來求指定兩點之間的最短路或者指定一個點到其他各個頂點的最短路徑也是可行的。

固然也有更快的算法,請看下一節:Dijkstra算法。

另外需要注意的是:Floyd-Warshall算法不能解決帶有「負權迴路」(或者叫「負權環」)的圖。因爲帶有「負權迴路」的圖沒有最短路。好比如下這個圖就不存在1號頂點到3號頂點的最短路徑。因爲1->2->3->1->2->3->…->1->2->3這樣路徑中,每繞一次1->-2>3這種環。最短路就會下降1。永遠找不到最短路。

事實上假設一個圖中帶有「負權迴路」那麼這個圖則沒有最短路。

081030elthvel6et6k886y.png

此算法由Robert W. Floyd(羅伯特·弗洛伊德)於1962年發表在「Communications of the ACM」上。

同年Stephen Warshall(史蒂芬·沃舍爾)也獨立發表了這個算法。Robert W.Floyd這個牛人是朵奇葩,他本來在芝加哥大學讀的文學,但是因爲當時美國經濟不太景氣。找工做比較困難,無奈之下到西屋電氣公司當了一名計算機操做員,在IBM650機房值夜班,並由此開始了他的計算機生涯。此外他還和J.W.J. Williams(威廉姆斯)於1964年共同發明了著名的堆排序算法HEAPSORT。

Robert W.Floyd在1987年得到了圖靈獎。

相關文章
相關標籤/搜索