最短路徑Floyd算法【圖文詳解】

Floyd算法算法

1.定義概覽vim

Floyd-Warshall算法(Floyd-Warshall algorithm)是解決任意兩點間的最短路徑的一種算法,能夠正確處理有向圖或負權的最短路徑問題,同時也被用於計算有向圖的傳遞閉包。Floyd-Warshall算法的時間複雜度爲O(N3),空間複雜度爲O(N2)。數組

 

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).算法描述:spa

a.從任意一條單邊路徑開始。全部兩點之間的距離是邊的權,若是兩點之間沒有邊相連,則權爲無窮大。   3d

b.對於每一對頂點 u 和 v,看看是否存在一個頂點 w 使得從 u 到 w 再到 v 比己知的路徑更短。若是是更新它。code

3).Floyd算法過程矩陣的計算----十字交叉法

方法:兩條線,從左上角開始計算一直到右下角 以下所示

給出矩陣,其中矩陣A是鄰接矩陣,而矩陣Path記錄u,v兩點之間最短路徑所必須通過的點

相應計算方法以下:

最後A3即爲所求結果

3.算法代碼實現

 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 }

算法時間複雜度:O(n3)

對上面代碼還能夠進一步優化?答案是確定的,不信你看下面這個小故事告訴你訣竅:

081028t67l8vd73686e68m.png

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



       上圖中有4個城市8條公路,公路上的數字表示這條公路的長短。請注意這些公路是單向的。咱們如今須要求任意兩個城市之間的最短路程,也就是求任意兩個點之間的最短路徑。這個問題這也被稱爲「多源最短路徑」問題。

 

       如今須要一個數據結構來存儲圖的信息,咱們仍然能夠用一個4*4的矩陣(二維數組e)來存儲。好比1號城市到2號城市的路程爲2,則設e[1][2]的值爲2。2號城市沒法到達4號城市,則設置e[2][4]的值爲∞。另外此處約定一個城市本身是到本身的也是0,例如e[1][1]爲0,具體以下。
081028o2n5ebn8hdeh9e5l.png

       如今回到問題:如何求任意兩點之間最短路徑呢?經過以前的學習咱們知道經過深度或廣度優先搜索能夠求出兩點之間的最短路徑。因此進行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。經過這個的例子,咱們發現每一個頂點都有可能使得另外兩個頂點之間的路程變短。好,下面咱們將這個問題通常化。

 

       當任意兩點之間不容許通過第三個點時,這些城市之間最短路程就是初始路程,以下。
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     }
8 }

 

在只容許通過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

 

       整個算法過程雖說起來很麻煩,可是代碼實現卻很是簡單,核心代碼只有五行:

 

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號點的最短路程。其實這是一種「動態規劃」的思想!

動態轉移方程:dp[k][i][j]=min(dp[k-1][i][j],dp[k-1][i][k]+dp[k-1][k][j]);

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

 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;
39 }

 有一點須要注意的是:如何表示正無窮。咱們一般將正無窮定義爲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)。使人很震撼的是它居然只有五行代碼,實現起來很是容易。正是由於它實現起來很是容易,若是時間複雜度要求不高,使用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在1978年得到了圖靈獎。

相關文章
相關標籤/搜索