一、Bellman-Ford
Bellman-Ford 算法是一種用於計算帶權有向圖中單源最短路徑(當然也可以是無向圖)。與Dijkstra相比的優點是,也適合存在負權的圖。
若存在最短路(不含負環時),可用Bellman-Ford求出,若最短路不存在時,Bellman-Ford只能用來判斷是否存在負環。
松弛:
每次松弛操作實際上是對相鄰節點的訪問(相當於廣度優先搜索),第n次松弛操作保證了所有深度為n的路徑最短。由於圖的最短路徑最長不會經過超過|V| - 1條邊,所以可知貝爾曼-福特算法所得為最短路徑,也可只時間復雜度為O(VE)。
負邊權操作:
與迪科斯徹算法不同的是,迪科斯徹算法的基本操作“拓展”是在深度上尋路,而“松弛”操作則是在廣度上尋路,這就確定了貝爾曼-福特算法可以對負邊進行操作而不會影響結果。
負權環判定:
因為負權環可以無限制的降低總花費,所以如果發現第n次操作仍可降低花銷,就一定存在負權環。
基本操作:
- 創建源頂點 v 到圖中所有頂點的距離的集合 distSet,為圖中的所有頂點指定一個距離值,初始均為 Infinite,源頂點距離為 0;
- 計算最短路徑,執行 V - 1 次遍歷;
- 對於圖中的每條邊:如果起點 u 的距離 d 加上邊的權值 w 小於終點 v 的距離 d,則更新終點 v 的距離值 d;
- 檢測圖中是否有負權邊形成了環,遍歷圖中的所有邊,計算 u 至 v 的距離,如果對於 v 存在更小的距離,則說明存在環(無向圖不能用這種方法判斷負環)
正確性:
Bellman-Ford 算法采用動態規划進行設計,實現的時間復雜度為 O(V*E),其中 V 為頂點數量,E 為邊的數量。簡單的說我們用
dis[k][v]表示經過前i個頂點到達v的最短路,易得轉移方程dis[k][v] = min(dis[k][v],dis[ k -1][u] + w)。未使用滾動數組優化空間時,實現的代碼如下:
1 int dis[maxv][maxv]; //dis[k][v];表示選取前k個時到達i的最短距離 2 struct Edge 3 { 4 int u, v, w; 5 }edge[maxv]; 6 int n, m; 7 8 void Bellman_Ford(int s) 9 { 10 memset(dis, INF, sizeof(dis)); 11 for (int i = 1; i <= n; i++) dis[i][s] = 0; 12 for (int k = 1; k <= n - 1; k++) 13 for (int i = 0; i < m; i++) 14 { 15 int u = edge[i].u, v = edge[i].v, w = edge[i].w; 16 dis[k][v] = min(dis[k][v], dis[k - 1][u] + w); 17 } 18 }
優化:
循環的提前退出:
在實際操作中,貝爾曼-福特算法經常會在未達到 |V| - 1 次前就出解,|V| -1 其實是最大值。於是可以在循環中設置判定,在某次循環不再進行松弛時,直接退出循環,進行負權環判定。
隊列優化:
即SPFA
二、SPFA
是一個用於求解有向帶權圖單源最短路徑的改良的貝爾曼-福特算法(當然也可以通過將每條邊換為兩條逆向的邊來用於無向圖)。這一算法被認為在隨機的稀疏圖上表現出色,並且極其適合帶有負邊權的圖。然而SPFA在最壞情況的時間復雜度與Bellman-Ford算法相同,因此在非負邊權的圖中仍然最好使用Dijkstra。
原理:
基於Bellman-Ford之外,再可以確定,松弛操作必定只會發生在最短路徑前導節點松弛成功過的節點上,用一個隊列記錄松弛過的節點,可以避免了冗余計算。
優化:
SPFA算法的性能很大程度上取決於用於松弛其他節點的備選節點的順序。我們注意到其與Dijkstra很像,一方面,優先隊列替換成普通的FIFO隊列,而另一方面,一個節可以多次進入隊列點。
事實上,如果 q 是一個優先隊列,則這個算法將極其類似於戴克斯特拉算法。然而盡管這一算法中並沒有用到優先隊列,仍有兩種可用的技巧可以用來提升隊列的質量,並且借此能夠提高平均性能(但仍無法提高最壞情況下的性能)。兩種技巧通過重新調整 q 中元素的順序從而使得更靠近源點的節點能夠被更早地處理。
距離小者優先(Small Lable First(SLF)):
將總是把v壓入隊列尾端改為比較dis[v]與dis[q.front()]的大小(為了避免出現隊列為空的操作,先將v壓入隊尾),並且在v較小時將v壓入隊列的頭端。
距離大者優先(Large Lable Last(LLL)):
我們更新隊列以確保隊列頭端的節點的距離總小於平均,並且任何距離大於平均的節點都將被移到隊列尾端。
改為DFS版:
dfs版spfa判環根據:若一個節點出現2次及以上,則存在負環。具有天然的優勢。由於是負環,所以無需像一般的spfa一樣初始化為極大的數,只需要初始化為0就夠了(可以減少大量的搜索,但要注意最開始時for一遍)。