1. 程式人生 > >dijkstra求最短路徑長度

dijkstra求最短路徑長度

概述

dijkstra演算法是單源最短路徑演算法的一種。

所謂單源,即在一個有向圖中,從一個節點出發,演算法可求該節點至所有可到達節點的最短路徑長度。與之相對的稱為非單源最短路,即演算法執行一次可求出任意節點至任意可到達節點的最短路長度,其代表是floyd演算法。單源最短路有兩種常見演算法,dijkstra演算法和bellman-ford演算法。前者只能用於求路徑權值全部為非負數的最短路,但後者是可以求邊權中出現負數的演算法。

既然非單源一次可以求出任意節點間的最短路,我們為什麼還要使用單源演算法呢?

因為floyd的時間複雜度為O(n^3),而dijkstra只有O(n^2)。如果要求出任意節點間的最短路,那麼兩個演算法似乎都是O(n^3),然而floyd似乎更好理解。


dijkstra演算法描述:
設G=(V,E)是一個有向圖,V表示頂點,E表示邊;它的每一條邊(i,j)屬於E,都有一個非負權W(i,j);在G中指定一個結點V0,要求求出從V0到G的每一個節點Vj(Vj屬於V)的最短路徑長度。

舉例

根據下圖,求從節點0開始到各節點的最短距離:


配合圖中右邊的過程:

  1. 首先我們先將從節點0出發的所有路徑長度標記為無窮大。
  2. 由於從節點0出發,故更新從節點0到其直接相鄰節點的路徑長度,0->1:10,0->3:30,0->4:100。
  3. 取出其中最小的0->1:10,並且我們已經清楚0->1的距離已經是最短的了。
    解釋:從0到1無非兩種走法,0直接到達1或0通過其他節點到達1。現在0到1的距離為10,其已小於0到其餘節點的距離,即0->C->1=(30或100)+C->1,0->C->1已經不可能小於現在的0->1了,故現在的0->1已經是最短的,不可能有更短的0->1。

    接著我們看下從節點1到其餘節點的路徑,明顯有一條1->2:50,通過1->2可以產生0->1->2:60這條路徑,該路徑長度遠小於現在表裡的0->2為無窮大,故更新0->2:60。
  4. 接下來取出表裡最小且沒有使用過的值,0->3:30。同理,從節點3到節點2、4有直接的路徑,得出0->3->2:50,其小於表中的0->2:60,故更新;0->3->4:90,其小於表中的0->4:100,故更新。
  5. 到此時,0->1:10,0->2:50,0->3:30,0->4:90,其中節點0、1、3已經被使用過,所以下面我們取出表裡的最小且沒有使用過的值,0->2:50。同理,節點2到4有一條直接路徑,0->2->4:100,但其大於表中的0->4:90,故不更新。


演算法步驟


步驟一:初始路徑長度均為無窮大,將出發節點作為節點B和A。
步驟二:找到A節點射出的路徑,將A節點作為中途節點,C為任意節點。當B->A->C<B->C,更新表中B->C的長度。
步驟三:從表中選出最小且未作為過A節點的值,將其對應的節點作為A;重複步驟二,直至所有節點均被作為過A節點。


程式碼實現

根據上圖的例子,給出對應的演算法,求解從節點0到各節點的最短路長度:

#include<stdio.h>
const int N=100;
const int INF=100000;              //INF假定為無窮大
int p[N][N],d[N];                  //p表示各節點間的距離,不存在路徑即為無窮大;d表示從出發節點到各節點的最短路徑長度

void dijkstra(int sec,int n)       //sec為出發節點,n為圖中的節點數
{
    int i,j,min,min_num;
    int vis[N]={0,};               //用於標記是否已作為過中途節點,0表示沒有,1表示有
    for(i=0;i<n;i++)               //初始化
    {
        d[i]=p[sec][i];
    }
    vis[sec]=1;d[sec]=0;           //出發節點到自己的距離永遠為0
    for(i=1;i<n;i++)
    {
        min=INF;
        for(j=0;j<n;j++)           //每次迴圈取出d陣列中的未被作為過中途節點且數值最小的
        {
            if(!vis[j]&&d[j]<min)
            {
                min=d[j];          //更新最小值
                min_num=j;         //更新最小值所對應的節點,即記錄下標
            }
        }
        vis[min_num]=1;            //標記該節點,表示其已被作為中途節點
        for(j=0;j<n;j++)           //迴圈,經過min_num節點到達是否有更小距離,如有更小距離則更新d陣列
        {
            if(d[j]>min+p[min_num][j])
            {
                d[j]=min+p[min_num][j];
            }
        }
    }
}
int main()
{
    int i,j,n=5;                   //n表示圖中的節點個數
    for(i=0;i<n;i++)               //程式用二維陣列p儲存各節點間的距離,這裡則進行初始化
    {
        for(j=0;j<n;j++)
        {
            p[i][j]=(i==j?0:INF);  //初始化:i到j路徑為無窮大或者i到i本身為0
        }
    }
    p[0][1]=10;p[0][3]=30;p[0][4]=100;p[1][2]=50;p[2][4]=50;p[3][2]=20;p[3][4]=60;  //p[i][j]表示節點i到節點j的距離
    dijkstra(0,n);                 //求從節點0出發到各節點的最短距離
    for(i=0;i<n;i++)               //列印從節點0出發到各節點的最短距離
    {
        printf(i==n-1?"%d\n":"%d ",d[i]);
    }
    return 0;
}

記錄路徑

如何在求最短路長度的同時記錄路徑呢?可以看這篇部落格“dijkstra求最短路並記錄路徑