1. 程式人生 > >Floyd_任意點之間的最短路徑演算法

Floyd_任意點之間的最短路徑演算法

一、演算法介紹:

  Floyd–Warshall(簡稱Floyd演算法)是一種著名的解決任意兩點間的最短路徑(All Paris Shortest Paths,APSP)的演算法。從表面上粗看,Floyd演算法是一個非常簡單的三重迴圈,而且純粹的Floyd演算法的迴圈體內的語句也十分簡潔。Floyd演算法可以說是Warshall演算法的擴充套件,三個for迴圈就可以解決問題,所以它的時間複雜度為O(n^3)。從本質上說,Floyd是一個動態規劃(Dynamic Programming,DP)思想的體現,網上不少文章介紹得很籠統,沒有解釋清楚基本原理。故在此獻上我的想法。

二、Floyd演算法:

  首先引入一個非常重要的概念:
  我們假設Dis(k,i,j)代表著從源點i到終點j的一個最短路徑值,且k是該路徑中所有點編號的最大值 (而不僅僅只是經過和不經過的問題)。即i到j的路徑中所有點的編號都小於等於k。

  有了這個概念之後,我們來看看是否能夠找出一個迭代關係。
  一定的是,對於Dis(k,i,j)的路徑中,可能沒有包含編號為k的點,也可能包含了編號為k的點,利用這僅有的兩種情況可以定義他的迭代關係。
  第一種情況下,由於沒有經過包含編號k的點,所以Dis(k,i,j) == Dis(k-1,i,j)
  第二種情況下,由於經過了編號為k的點,我們將路徑以k點分為兩部分Dis(k-1,i,k)和Dis(k-1,k,j),即等式“Dis(k-1,i,k)+Dis(k-1,k,j) == Dis(k,i,j)”

成立。由於k作為兩部分前者的終點後者的源點,所以兩部分中其他的點都應該不大於k-1這個值,所以編號的最大值都是k-1。同等最大編號情況下的兩條最短子路徑相加必然等於同等最大編號情況下的總路徑最小值。
  至此,我們在這僅有的兩種情況中進行選擇,路徑更小的就可以更新為源點i終點j且路徑中各個點的編號最大值為k的最短路徑了( - -真繞口)。即有Dis(k,i,j)=Min{ Dis(k-1,i,k)+Dis(k-1,k,j), Dis(k-1,i,j) }

  現在,我們有了迭代關係,缺少的只有初始條件與結束條件。
  對於初始條件,i和j代表各個點,所以取值範圍只要在點的編號範圍內即可。由於k-1的存在,k的最小值為1。此時有Dis(1,i,j) = Min{ Dis(0,i,1) + Dis(0,1,j), Dis(0,i,j) }

。Dis(0,i,j)代表經過點編號最大值為0的點,顯然不存在這樣的編號,那麼Dis(0,i,j)就可以順理成章地表示為i到j不經過任何中間點的直接距離,也就是我們會輸入的鄰接矩陣的值。
  對於結束條件,當k達到編號的最大值時,Dis(k,i,j)表示的就是經過點編號最大值為所有編號的最大值的路徑,此時也就是源點i到終點j的最大值了,因為k為最大值已經包含所有的點了。
  
  這樣一來,我們就有了完整的遞推關係。要求任意點到任意點的最短距離就是當k值為最大值時候的Dis(k,i,j)了。
  整個過程梳理一下:
  1、用Dis[0,i,j]記錄每一對頂點的最短距離。
  2、依次掃描每一個點,並以該頂點的編號值為k再遍歷所有每一對頂點Dis(k,i,j)的值,看看是否可用過該頂點的路徑讓這對頂點間的距離更小。

    for(int k = 0; k < N; k++)
        for(int i = 1; i <= N; i++)
            for(int j = 1; j <= N; j++)
                d[i][j] = min(d[i][j], d[i][k]+d[k][j]);

  PS:1.整個演算法中k-1代表的不是數字意義上的-1而是編號的更小一位。如編號分別為(1,10,100,1000)當k代表編號1000時,k-1代表編號100。根據實際情況為準。
    2.由於計算到Dis(k,i,j)時, 任意頂點間的路徑不大於k-1的值已經全部求出(k是遞增的),所以可以儲存該演算法的正確性。

三、例題:

  http://poj.org/problem?id=2139
  題意:奶牛們最近要拍電影了……(黑白電影麼)
  1、若兩個的奶牛一起拍過電影,則他們之間的距離為1;
  2、若兩隻奶牛a、b沒有一起拍過電影,但與另有一隻奶牛c都和他們拍過電影,則a、b的距離為2(通過c得到)。
  求奶牛的與其他奶牛的距離的平均值的一百倍的整數。

所以我做為例題的題目都是基礎題,僅僅是做為演算法練手。。

六、Talk is cheap,show me the code(例題程式碼)

#include <iostream>
using namespace std;

#define INFINITY 301;

void Floyd(int **p, int N, int **path = NULL)
{
    int i, j, k;
    for (k = 0; k < N; k++)
    {
        for (i = 0; i < N; i++)
        {
            for (j = 0; j < N; j++)
            {
                if (i != j && p[i][j] > p[i][k] + p[k][j])
                {
                    p[i][j] = p[i][k] + p[k][j];
#ifdef PATH
                    path[i][j] = path[k][j];
#endif
                }

            }
        }
    }
}

int main(void)
{
    int N, M;
    cin >> N >> M;
    int **Cows = new int*[N];
    int i, j;
    for (i = 0; i < N; i++)
    {
        Cows[i] = new int[N];
        for (j = 0; j < N; j++)
        {
            if (i == j)
                Cows[i][j] = 0;
            else
                Cows[i][j] = INFINITY;
        }
    }

    int *input;
    while (M--)
    {
        int num;
        cin >> num;
        input = new int[num];
        for (i = 0; i < num; i++)
            cin >> input[i];
        for (i = 0; i < num; i++)
            for (j = i + 1; j < num; j++)
                Cows[input[i] - 1][input[j] - 1] = Cows[input[j] - 1][input[i] - 1] = 1;
    }
    //Floyd
    Floyd(Cows, N);
    /*
    for (i = 0; i < N; i++)
    {
        for (j = 0; j < N; j++)
            cout << Cows[i][j]<<" ";
        cout << endl;
    }*/
    int min = N * INFINITY;
    for (i = 0; i < N; i++)
    {
        int sum = 0;
        for (j = 0; j < N; j++)
            if (i != j)
                sum += Cows[i][j];
        min = sum < min ? sum : min;
    }
    cout << min * 100 / (N - 1) << endl;
    system("PAUSE");
    return 0;
}