單源最短路徑


一、Dijkstra算法

Dijkstra算法是解決帶權重的有向圖最短路徑問題,要求所有邊權重為非負值。

以下是算法導論上給出的偽碼,采用了是貪心策略實現的,總是尋找集合V-S(S集合是加入)中最近的節點加入到S集合中,算法時間復雜度依賴於最小優先隊列的實現方式。

Dijkstra(G,w,s)
    for each vertex v in G.V
        v.d=FIN;   //最短路徑估計
        v.π=NIL; //前驅節點        
    s.d=0;   //到源節點距離為0
    S=NULL;
    Q=G.V;
    while !Q.empty
        u=EXTRACT-MIN(Q)   //最小優先隊列
        S=SU{u}
        for each vertex v in G.Adj[u]
           RELAX(u,v,w)


RELAX(u,v,w)     //松弛操作
    if v.d>u.d+w(u,v)    
        v.d=u.d+w(u,v)    
        v.π=u

下面是C++的實現,時間復雜度是O(N^2),N為節點數。

 1 /***prev數組保存已加入集合節點的前驅,dist數組保存每個節點到源節點的距離,vex邊數,v源節點***/
 2 const int INF=0xffff;
 3 const int MAX=100;
 4 void Dijkstra(vector<vector<int>>G,int *prev,int *dist,int vex,int v)
 5 {
 6     bool s[MAX];   //記錄節點是否加入集合
 7     for(int i=0;i<vex;i++)
 8     {
 9         dist[i]=G[v][i];  //源節點到每個節點的距離
10         s[i]=0;           //初始化未使用節點
11         if(dist[i]=INF)
12             prev[i]=0;    //設置前驅節點
13         else
14             prev[i]=v;
15     }
16     dist[v]=0;
17     s[v]=1;               //將源節點加入集合
18 
19     for(int i=1;i<vex;i++)
20     {
21         int temp=MAX;
22         int u=v;
23         for(int j=0;j<vex;j++)    //找出dist中最小值
24         {
25             if((!s[j])&&dist[j]<temp)
26             {
27                 u=j;
28                 temp=dist[j];
29             }
30         }
31         s[u]=1;           //加入集合
32 
33         for(int j=0;j<=vex;j++)       //松弛操作
34         {
35             if((!s[j])&&G[u][j]<MAX)
36             {
37                 if(dist[u]+G[u][j]<dist[j])
38                 {
39                     dist[j]=dist[u]+G[u][j];
40                     prev[j]=u;
41                 }
42             }
43         }
44     }
45 }

取終點將前驅數組逆序就可以得到最短路徑的路線圖了。

 

二、Bellman-Ford算法

該算法解決的是一般的單源最短路徑問題,可以允許邊的權重為負值,算法返回一個布爾值,表明是否存在一個從源節點可以到達的權重為負值的環路。

算法導論給出的偽碼,時間復雜度為O(V*E)

Bellman-Ford(G,w,s)
    for each vertex v in G.V
        v.d=FIN;   //最短路徑估計
        v.π=NIL; //前驅節點        
    s.d=0;   //到源節點距離為0
    for i=1 to |G.V|-1
        for each edge(u,v) in G.E
            RELAX(u,v,w)      //與dijstra算法一樣的松弛操作
    for each edge(u,v) in G.E  //判斷是否有存在權重為負值的環路
        if(v.d>v.d+w(u.v))
            return FALSE
    return TRUE

理解Bellman-Ford算法,首先我們要理解松弛操作,下面給出算法導論給出的路徑松弛性質:

圖G從源節點s到節點uk的任意一條最短路徑p=<v0,v1,v2,…,vk>,圖G在初始化后,在進行一系列松弛操作,其中包括<v0,v1><v1,v2><v2,v3>…<vk-1,vk>的次序進行松弛操作后,我們可以得到源節點到vk的最短路徑(權重),並且所得的值會一直保持成立。該性質的成立與其他邊的松弛操作及順序無關。

 

路徑松弛性質的證明可以用歸納法證明:

第一步,源節點s到s的最短路徑權重就是0,初始化后將不會改變;

歸納步,假定s到vi-1的最短路徑權重為k,我們經過(vi-1,vi)松弛操作后,必然有s到vi的最短路徑(權重)就可以得到了(收斂性質),並且其他操作不會改變這個結果。

 

因為經過|G.V|-1的循環的松弛操作,必定包括<v0,v1><v1,v2><v2,v3>…<vk-1,vk>的次序的松弛操作,因而Bellman-Ford算法合理的。

下面給出的C++實現:

 1 typedef struct Edge {
 2     int u,v;
 3     int weight;
 4 }Edge;        //邊集來描述圖
 5 
 6 
 7 bool Bellman_Ford(Edge *edge,int *dist,int *prev,int vex,int v,int edgenum)
 8 {
 9     bool flag=1;
10     for(int i=0;i<vex;i++)
11     {
12         dist[i]=MAX;  //源節點到每個節點的距離
13     }
14     dist[v]=0;
15     for(int i=0;i<edgenum;i++)
16     {
17         if(edge[i].u==v)
18          {
19             dist[edge[i].v]=edge[i].weight;   //初始化dist數組
20             prev[edge[i].v]=v;                //設置前驅節點
21          }
22     }
23     
24     for(int i=1;i<vex;i++)                   //松弛操作
25     {
26         for(int j=0;j<edgenum;j++)
27         {
28             if(dist[edge[j].v]>dist[edge[j].u]+edge[j].weight)
29             {
30                 dist[edge[j].v]=dist[edge[j].u]+edge[j].weight;
31                 prev[edge[j].v]=edge[j].u;
32             }    
33         }
34     }
35     for(int j=0;j<edgenum;j++)   //檢驗是否含有權重為負的環路
36     {
37         if(dist[edge[j].v]>dist[edge[j].u]+edge[j].weight)
38             flag=0;
39     }
40     return flag;
41 } 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM