最短路徑算法

原文鏈接 http://dongzi85.blog.163.com/blog/static/8079890520089288468222/

最短路徑算法

分類:

  • 無迴路網絡:拓撲排序法
  • 無負權網絡:Dijkstra算法,Bellman-Ford算法
  • 帶負權網絡:Bellman-Ford算法
  • 所有點對之間的最短路徑:Floyd-Warshall算法

在圖中存在負權的情況下,最短路徑不一定存在。可以證明:
  • [定理]:在無向連通圖中,兩點間的最短路徑存在當且僅當不含負權圈(往死裏轉吧,無窮小)。
由於問題變的複雜了,我們無法找到一個像Dihijkstra算法那樣時間複雜度這麼低的方法,但是仍然有一個不錯的方法,就是Bellman-Ford迭代算法。

Bellman Ford算法(SSSP):
基本思想:如果兩點間有最短路,那麼每個頂點最多經過一次。如果一個頂點經過兩次,那麼必定走了一個圈,如果這是個正圈,顯然是不合算的;如果是負圈,則根據上述定理,不存在最短路徑。
初始化d[起點]=0,d[其他點]= 無窮

for k=1 to n-1 do
for 每條邊 (u,v) do
    if (d[u]<無窮) and (d[u]+w(u,v)<d[v])then d[v]=d[u]+w(u,v);

時間複雜度O(mn)。

Floyd-Warshall算法(ASSP):

求多源、無負權邊的最短路。用矩陣記錄圖。時效性較差,時間複雜度O(V^3)。
Floyd-Warshall算法(Floyd-Warshall algorithm)是解決任意兩點間的最短路徑的一種算法,可以正確處理
有向圖最短路徑問題。
Floyd-Warshall算法的時間複雜度爲O(N^3),空間複雜度爲O(N^2)。
Floyd-Warshall的原理是動態規劃:

考慮最短路的最優子結構性質。設d[i,j,k]是從節點i到節點j的最短路徑,且只允許經過[1...k]中的節點,考察節點k,有兩種情況:

  • 經過k:則d[i,j,k]=d[i,k,k-1]+d[k,j,k-1]
  • 不經過k:則d[i,j,k]=d[i,j,k-1]
於是,d[i,j,k]=min{d[i,k,k-1]+d[k,j,k-1], d[i,j,k-1]}

for k=1 to n do
   for i=1 to ndo
      for j=1 to ndo
        if (d[i,k]<無窮) and(d[k,j]<無窮)and
d[i,k]+d[k,j]<d[i,j]then
            d[i,j]=d[i,k]+d[k,j];

時間複雜度爲O(n3).

最短路算法總結:
  • 標號修正算法(Bellman-Ford):算法迭代式的,標號都是臨時的,算法思想是不斷逼近最優解,最後一步達到最優解。
  • 標號設定算法(Dijkstra):算法執行過程就是不斷把臨時標號設定成爲永久標號的過程。
  • 標號設定算法時間複雜度低,但適用範圍小,例如Dijkstr算法就不適用於含負權邊的情況。標號修正算法適用範圍相對較廣,但時間複雜度較高。

 

附模板(原文鏈接 http://www.cnblogs.com/touchsunlight/archive/2010/08/09/1795816.html)

完全最短路徑(Floyd算法):[複雜度:O(n^3)]

// 矩陣mat初始值INT_MAX

// 結果 mat[i][j] 爲點i到j的最短路徑

// mat[i][j] == INT_MAX時候爲不可到達

 

void Floyd(int n)

{ 

    int i, j, k;

    for (k = 1; k <= n; k++)

        for (i = 1; i <= n; i++)

            for (j = 1; j <= n; j++)

                if (mat[i][k] != INT_MAX &&

                    mat[k][j] != INT_MAX &&

                    mat[i][k]+mat[k][j] < mat[i][j])

                        mat[i][j] = mat[i][k] + mat[k][j];

}

單源最短路徑Dijkstra算法:

// mat初始值爲INT_MAX,即不可到達

// s表示起始點,p重點,n節點個數,返回s到p的最短路徑

// 當返回結果爲INT_MAX時,表示不可達

// 結果dis爲第s點到其他點的最短路徑

 

int dis[MAXN];

bool flag[MAXN];

int mat[MAXN][MAXN];

 

void Dijkstra(int s, int p, int n)

{

    int i, j;

    for (i = 1; i <= n; i++)

        dis[i] = mat[s][i], flag[i] = false;

    flag[s] = true, dis[s] = 0;

 

    for (i = 1; i <= n; i++)

    {

        int k = s, t = INT_MAX;

        for (j = 1; j <= n; j++)

            if (!flag[j] && dis[j] < t)

                k = j, t = dis[j];

        flag[k] = true;

        for (j = 1; j <= n; j++)

            if (!flag[j] && mat[k][j] != INT_MAX

                && dis[k] + mat[k][j] < dis[j])

                    dis[j] = dis[k] + mat[k][j];

    }

}

Bellman-Ford算法:

適用範圍:

  1. 單源最短路徑(從源點s到其它所有頂點v);
  2. 有向圖&無向圖(無向圖可以看作(u,v),(v,u)同屬於邊集E的有向圖);
  3. 邊權可正可負(如有負權迴路輸出錯誤提示);
  4. 差分約束系統;

算法描述:

  1. 對每條邊進行|V|-1次Relax操作;
  2. 如果存在(u,v)∈E使得dis[u]+w<dis[v],則存在負權迴路;否則dis[v]即爲s到v的最短距離,pre[v]爲前驅。
For i:=1 to |V|-1 do //v爲頂點數

  For 每條邊(u,v)∈E do  //對每條邊進行遍歷

    Relax(u,v,w);

For 每條邊(u,v)∈E do

  If dis[u]+w<dis[v] Then Exit(False)

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章