1. 程式人生 > >Floyd演算法詳講

Floyd演算法詳講

Floyd–Warshall(簡稱Floyd演算法)是一種著名的解決任意兩點間的最短路徑(All Paris Shortest Paths,APSP)的演算法。從表面上粗看,Floyd演算法是一個非常簡單的三重迴圈,而且純粹的Floyd演算法的迴圈體內的語句也十分簡潔。我認為,正是由於“Floyd演算法是一種動態規劃(Dynamic Programming)演算法”的本質,才導致了Floyd演算法如此精妙。因此,這裡我將從Floyd演算法的狀態定義、動態轉移方程以及滾動陣列等重要方面,來簡單剖析一下圖論中這一重要的基於動態規劃的演算法——Floyd演算法。

Floyd演算法的基本思想如下:從任意節點A到任意節點B的最短路徑不外乎2種可能,1是直接從A到B,2是從A經過若干個節點X到B。所以,我們假設Dis(AB)為節點A到節點B的最短路徑的距離,對於每一個節點X,我們檢查Dis(AX) + Dis(XB) < Dis(AB)是否成立,如果成立,證明從A到X再到B的路徑比A直接到B的路徑短,我們便設定Dis(AB) = Dis(AX) + Dis(XB),這樣一來,當我們遍歷完所有節點X,Dis(AB)中記錄的便是A到B的最短路徑的距離。

來看看程式碼:

for ( int i = 0; k < 節點個數; ++i )
{
    for ( int j = 0; i < 節點個數; ++j )
    {
        for ( int k = 0; j < 節點個數; ++k )
        {
            if ( Dis[i][k] + Dis[k][j] < Dis[i][j] )
            {
                // 找到更短路徑
                Dis[i][j] = Dis[i][k] + Dis[k][j];
            }
        }
    }
}

Floyd演算法本質上是DP,即對於每個(可能的)新增的節點k,來更新(可能的)節點i到j的最短距離。
那就有人要問了,為什麼新增節點 k 的迴圈要放在最外層呢? 因為如果放到內層,這樣便過早的把i到j的最短路徑確定下來了,而當後面存在更短的路徑時,已經不再會更新了。

用圖來舉個例子:
這裡寫圖片描述
圖中紅色數字代表權值,如果我們在內層檢查所有節點時,我們只能發現一條路徑A->B,距離為9,顯然這樣是不正確的,正確的最短路徑是A->D->C->B,路徑距離為6。之所以這樣就是因為把檢查所有節點的迴圈放到了內層,造成了找到一條路徑就過早的確定下來,而這一條路徑不一定是最短路徑。

如果還不明白,舉個例項:

這裡寫圖片描述

程式碼:
//Floyd
#include<set>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define FOR(i,n,x) for(int i=x; i<=n; i++)
#define Inf 1<<29
using namespace std;
int mp[100][100],n,m;
int main()
{
    while(~scanf("%d%d",&n,&m)!=EOF)
    {
        FOR(i,n,1)
        {
            FOR(j,n,1)
            {
                if(i!=j)
                    mp[i][j]=Inf;
                    else mp[i][j]=0;
            }
        }
        FOR(i,m,1)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            mp[a][b]=c;

        }
        FOR(k,n,1)//列舉任意一對節點
        {
            FOR(i,n,1)//起點
            {
                FOR(j,n,1)//終點
                {
                    if(i!=j && j!=k)
                    {
                        mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]);
                    }
                }
            }
        }
        FOR(i,n,1)
        {
            FOR(j,n,1)
            {
                printf("dis %d %d = %d\n",i,j,mp[i][j]);
            }
        }
    }
    return 0;
}

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

輸入

4 4
1 4 1
4 2 3
4 3 1
3 2 1

輸出

dis 1 1 = 0
dis 1 2 = 3
dis 1 3 = 2
dis 1 4 = 1
dis 2 1 = 536870912
dis 2 2 = 0
dis 2 3 = 536870912
dis 2 4 = 536870912
dis 3 1 = 536870912
dis 3 2 = 1
dis 3 3 = 0
dis 3 4 = 536870912
dis 4 1 = 536870912
dis 4 2 = 2
dis 4 3 = 1
dis 4 4 = 0

Floyd算分的優缺點:
Floyd演算法適用於APSP(All Pairs Shortest Paths,多源最短路徑),是一種動態規劃演算法,稠密圖效果最佳,邊權可正可負。此演算法簡單有效,由於三重迴圈結構緊湊,對於稠密圖,效率要高於執行|V|次Dijkstra演算法,也要高於執行V次SPFA演算法。
優點:容易理解,可以算出任意兩個節點之間的最短距離,程式碼編寫簡單。
缺點:時間複雜度比較高,不適合計算大量資料
侷限性:floyd演算法可以有負權值的邊,但不能有包含負權值邊組成的迴路

參考: