一、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 }
