Floyd最短路算法

此算法由Robert W. Floyd(羅伯特·弗洛伊德)於1962年發表在“Communications of the ACM”上。同年Stephen Warshall(史蒂芬·沃舍爾)也獨立發表了這個算法。Robert W.Floyd這個牛人是朵奇葩,他原本在芝加哥大學讀的文學,但是因爲當時美國經濟不太景氣,找工作比較困難,無奈之下到西屋電氣公司當了一名計算機操作員,在IBM650機房值夜班,並由此開始了他的計算機生涯。

Floyd算法又稱爲插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的算法,與Dijkstra算法類似,用於求每一對頂點的最短路徑。有向圖無向圖均可,也可以有負權邊。


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

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

現在需要一個數據結構來存儲圖的信息,我們仍然可以用一個4*4的矩陣(二維數組e)來存儲。比如1號城市到2號城市的路程爲2,則設e[1][2]的值爲2。2號城市無法到達4號城市,則設置e[2][4]的值爲∞。另外此處約定一個城市自己是到自己的也是0,例如e[1][1]爲0


現在問題來了如何求任意兩點之間的最短路徑呢?

如果求任意兩點之間的最短路徑,兩點之間可以直接到達但卻不是最短的路徑,要讓任意兩點(例如從頂點a點到頂點b)之間的路程變短,只能引入第三個點(頂點k),並通過這個頂點k中轉即a->k->b,纔可能縮短原來從頂點a點到頂點b的路程。那麼這個中轉的頂點k是1~n中的哪個點呢?甚至有時候不只通過一個點,而是經過兩個點或者更多點中轉會更短。比如上圖中從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號頂點的情況下,任意兩點之間的最短路程更新爲:


在只允許經過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循環,代碼實現如下。

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和2號兩個頂點的情況下任意兩點之間的最短路程。如何做呢?我們需要在只允許經過1號頂點時任意兩點的最短路程的結果下,再判斷如果經過2號頂點是否可以使得i號頂點到j號頂點之間的路程變得更短。即判斷e[i][2]+e[2][j]是否比e[i][j]要小,在只允許經過1和2號頂點的情況下,任意兩點之間的最短路程更新爲:


判斷e[i][2]+e[2][j]是否比e[i][j]要小,代碼實現爲如下。

//經過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號頂點進行中轉的情況下,求任意兩點之間的最短路程。任意兩點之間的最短路程更新爲:


最後允許通過所有頂點作爲中轉,任意兩點之間最終的最短路程爲:

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

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]; 
另外需要注意的是:Floyd算法不能解決帶有“負權迴路”(或者叫“負權環”)的圖,因爲帶有“負權迴路”的圖沒有最短路。例如下面這個圖就不存在1號頂點到3號頂點的最短路徑。因爲1->2->3->1->2->3->…->1->2->3這樣路徑中,每繞一次1->-2>3這樣的環,最短路就會減少1,永遠找不到最短路。其實如果一個圖中帶有“負權迴路”那麼這個圖則沒有最短路。

Floyd優缺點分析

優點:容易理解,可以算出任意兩個節點之間的最短距離,代碼編寫簡單。
缺點:時間複雜度比較高(n3),不適合計算大量數據。

Floyd算法與Dijkstra算法的不同

1.Floyd算法是求任意兩點之間的距離,是多源最短路,而Dijkstra(迪傑斯特拉)算法是求一個頂點到其他所有頂點的最短路徑,是單源最短路。
2.Floyd算法可以算帶負權的,而Dijkstra(迪傑斯特拉)算法是不可以算帶負權的。並且Floyd算法不能算負權迴路。
3.Dijkstra(迪傑斯特拉)算法時間複雜度一般是o(n^2),Floyd算法時間複雜度是o(n^3),Dijkstra(迪傑斯特拉)算法比Floyd算法塊。
4.Floyd算法屬於動態規劃,Dijkstra(迪傑斯特拉)算法屬於貪心算法。

#include<stdio.h>   
int main()   
{   
    int e[10][10];
	int k,i,j,n,m,t1,t2,t3   
    int inf=99999999;//用inf(infinity的縮寫)存儲一個我們認爲的正無窮值   
    scanf("%d %d",&n,&m);//讀入n和m,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算法核心部分 
    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;   
} 


發佈了24 篇原創文章 · 獲贊 28 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章