1. 程式人生 > >Warshall(弗洛伊德演算法)

Warshall(弗洛伊德演算法)

    簡介:Floyd演算法又稱為插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的演算法,與Dijkstra演算法類似。該演算法名稱以創始人之一、1978年圖靈獎獲得者、斯坦福大學計算機科學系教授羅伯特·弗洛伊德命名。

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

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

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

我們回到最開始的問題,最短路徑問題,如何求任意兩點之間的最短路徑呢?

我們通過之前的學習,我們知道通過深搜或者廣搜都可以求出兩點的最短路徑,所以進行n^2 遍深搜或廣搜,即對每個點都進行一次深搜或者廣搜,我們就可以求得最短路徑。但是我們最常用求最短路徑的演算法的就是bellman-ford,dijkstra,spfa,floyd

演算法。

如果求任意兩點之間的最短路徑,兩點之間可以直接到達但卻不是最短的路徑,要讓任意兩點(例如從頂點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號頂點,求任意兩點的最短路徑我們應該怎麼求呢??

此時我們只需判斷e[i][1] + e[1][j] 是否比e[i][j] 要小即可。我們來說明一下e[i][j] 和 e[i][1] + e[1][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號頂點的情況下,任意兩點之間的路程更新為:

通過上圖我們發現,在只通過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號頂點
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號頂點的情況下,任意兩點之間的路徑更新為:

通過上圖我們可以看出來,在相比只允許1號頂點進行中轉的情況,這裡允許通過1號和2號頂點進行中轉,使得e[1][3] 和e[4][3]的路程變得更短了。

同理,我們在只允許通過1,2,3號頂點的情況下,求任意兩點之間的最短路程。任意兩點的最短路程更新為:

最後是允許所有頂點作為中轉,任意兩點的最終路程為:

整個演算法過程雖然說起來很麻煩,但是核心程式碼就那麼幾行,不信你看:

for(int k = 1 ; k <= n ; k ++)
{
        for(int i = 1 ; i <= n ; i ++)
        {
                for(int j = 1 ; j <= n ; j ++)
                {
                        if(e[i][j] > r[i][k] + e[k][j])
                                e[i][j] = e[i][k] + e[k][j];
                 }
        }
}

這個程式碼的基本思路就是我們從最開始的只允許經過1號頂點進行中轉,接下來只允許1,2進行中轉。。。。。允許經過1~n 號所有的頂點進行中轉,求任意兩點的最短路徑。

這個演算法的完整程式碼:

#include<bits/stdc++.h>
using namespace std;
const int INF = 99999999;
int main()
{
        int e[10][10] , n , m , t1 , t2 , t3;
        cin>>n>>m;  //n表示頂點個數,m表示邊的條數
        for(int i = 1 ; i <= n ; i ++)
        {
                for(int j = 1 ; j <= n ; j ++)
                {
                        if(i == j)
                                e[i][j] = 0 ;
                        else
                                e[i][j] = INF;
                }
        }
        for(int i = 1 ; i <= m ; i ++)
        {
                cin>>t1>>t2>>t3;
                e[t1][t2] = t3;
        }

        //核心程式碼
        for(int k = 1 ; k <= n ; k ++)
        {
                for(int i = 1 ; i <= n ; i ++)
                {
                        for(int 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(int i = 1 ; i <= n ; i ++)
        {
                for(int j = 1 ; j <= n ; j ++)
                {
                        printf("%3d",e[i][j]);
                }
                cout<<endl;
        }
        return 0 ;
}

/*
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
*/
我們來分析一下演算法:

Floyd優缺點分析:

優點:比較容易容易理解,可以算出任意兩個節點之間的最短距離,程式碼編寫簡單。
缺點:時間複雜度比較高(n3),不適合計算大量資料,當資料稍微大點兒的時候就可以選擇其他的演算法來解決問題了,不然也會是超時。

Floyd演算法與Dijkstra演算法的不同

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

例題:

poj 3660

hdu 2544