Bellman-Ford演算法
Bellman-Ford可以用來解決含有負權圖的單源最短路徑(Dijkstra不能解決這種問題)。程式碼簡單,但是效率低。具體的過程就是不停的鬆弛,每次鬆弛進行一次更新。如果更新n次仍然還可以更新,說明圖中存在負環,直接跳出就可以。這種情況下無解。
Bellman-Ford的時間複雜度是O(ve).
虛擬碼如下:
1 Bellman-Ford(G,w,s) :{ //圖G ,邊集 函式 w ,s為源點
2 for each vertex v ∈ V(G) do //初始化 1階段
3 d[v] ←+∞
4 d[s] ←0; //1階段結束
5 for i=1 to |v|-1 do //2階段開始,雙重迴圈。
6 for each edge(u,v) ∈E(G) do //邊集陣列要用到,窮舉每條邊。
7 If d[v]> d[u]+ w(u,v) then //鬆弛判斷
8 d[v]=d[u]+w(u,v) //鬆弛操作 2階段結束
9 for each edge(u,v) ∈E(G) do
10 If d[v]> d[u]+ w(u,v) then
11 Exit false
12 Exit true
13 }
SPFA演算法
因為Bellman-Ford的演算法冗餘的部分太多,有很多不必要的計算。所以可以用佇列對其進行優化,這就是我們所說的SPFA演算法。由西南交大的段凡丁在1994年提出。SPFA同樣是解決含有負權圖的單元最短路徑。其實就是對Bellman-Ford的優化。據說還有一些八卦的訊息,說SPFA其實就是Bellman-Ford演算法,而現在流傳的Bellman-Ford是山寨版,這裡就不深究了,哈。
SPFA演算法的思路就是:設立一個佇列,優化佇列中的元素u的dis[u],並對每一個與u相連的節點v進行鬆弛。如果dis[v] > dis[u] + w(u, v),dis[v] = dis[u] + w(u, v),並且如果當前的v不在佇列裡邊(inqueue[v] == false),則v入佇列並標記inqueue[v] = true。不斷鬆弛,直到佇列為空。
這樣看來SPFA很想BFS,其實寫法上SPFA類似BFS,不過有一點不同的是。對佇列中的節點u取出並且鬆弛完一次與u相鄰的所有節點v以後,將u再次標記為不在佇列,即inqueue[u] = false;這樣才能實現對u(也就是每一個節點)進行多次鬆弛,直到佇列為空。
當然,如果圖中含有負環,鬆弛會一直進行下去(總能找到div[v] > div[u] + w(u, v))。從Bellman-Ford中知道,一個節點最多被鬆弛n次,如果超過,則說明有負環,無解。這裡可以加一個cnt[]記錄每一個節點出現過的次數。保證cnt[i]<=n,否則跳出。
SPFA演算法期望的時間複雜度是O(ke),其中k是每個節點盡對的平均次數。k一般是小於2的。
實現程式碼如下:
1 bool spfa() {
2 int i, u, v, z;
3 for(i = 0; i < gv; ++i) {
4 dis[i] = inf; cnt[i] = 0; inq[i] = false;
5 }
6 q.push(s); inq[s] = true;
7 dis[s] = 0; cnt[s]++;
8
9 while(!q.empty()) {
10 u = q.front();
11 for(i = head[u]; i; i = g[i].next) {
12 v = g[i].to; z = g[i].val;
13 if(dis[v] > dis[u] + z) {
14 dis[v] = dis[u] + z;
15 if(!inq[v]) {
16 cnt[v]++;
17 inq[v] = true;
18 if(cnt[v] > n) return false; //存在負環
19 q.push(v);
20 }
21 }
22 }
23 inq[u] = false; //重新標記為false,實現多次鬆弛
24 q.pop();
25 }
26 return true;
27 }
SPFA演算法有兩個優化演算法 SLF 和 LLL:
SLF:Small Label First 策略,設要加入的節點是j,隊首元素為i,若dist(j)<dist(i),則將j插入隊首,否則插入隊尾。
LLL:Large Label Last 策略,設隊首元素為i,佇列中所有dist值的平均值為x,若dist(i)>x則將i插入到隊尾,查詢下一元素,直到找到某一i使得dist(i)<=x,則將i出對進行鬆弛操作。
SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高約 50%。
總體比較:
在Bellman-Ford演算法中,要是某個點的最短路徑估計值更新了,那麼我們必須對所有邊指向的終點再做一次鬆弛操作;
在SPFA演算法中,某個點的最短路徑估計值更新,只有以該點為起點的邊指向的終點需要再做一次鬆弛操作。
spfa演算法能夠避免很多冗餘的鬆弛,使O(ve)的複雜度降到O(ke).
AC_Von原創,轉載請註明出處:http://www.cnblogs.com/vongang/archive/2012/03/05/2380127.html