原文連接: https://ethsonliu.com/2018/04...
SPFA(Shortest Path Faster Algorithm)算法,是西南交通大學段凡丁於 1994 年發表的,其在 Bellman-ford 算法的基礎上加上一個隊列優化,減小了冗餘的鬆弛操做,是一種高效的最短路算法。html
設立一個隊列用來保存待優化的頂點,優化時每次取出隊首頂點 u,而且用 u 點當前的最短路徑估計值dist[u]
對與 u 點鄰接的頂點 v 進行鬆弛操做,若是 v 點的最短路徑估計值dist[v]
能夠更小,且 v 點不在當前的隊列中,就將 v 點放入隊尾。這樣不斷從隊列中取出頂點來進行鬆弛操做,直至隊列空爲止。(所謂的鬆弛操做,簡單來講,對於頂點 i,把dist[i]
調整更小。更多解釋請參考百科:鬆弛操做)ios
而其檢測負權迴路的方法也很簡單,若是某個點進入隊列的次數大於等於 n,則存在負權迴路,其中 n 爲圖的頂點數。c++
#include <iostream> #include <queue> #include <stack> using namespace std; int matrix[100][100]; // 鄰接矩陣 bool visited[100]; // 標記數組 int dist[100]; // 源點到頂點 i 的最短距離 int path[100]; // 記錄最短路的路徑 int enqueue_num[100]; // 記錄入隊次數 int vertex_num; // 頂點數 int edge_num; // 邊數 int source; // 源點 bool SPFA() { memset(visited, 0, sizeof(visited)); memset(enqueue_num, 0, sizeof(enqueue_num)); for (int i = 0; i < vertex_num; i++) { dist[i] = INT_MAX; path[i] = source; } queue<int> Q; Q.push(source); dist[source] = 0; visited[source] = 1; enqueue_num[source]++; while (!Q.empty()) { int u = Q.front(); Q.pop(); visited[u] = 0; for (int v = 0; v < vertex_num; v++) { if (matrix[u][v] != INT_MAX) // u 與 v 直接鄰接 { if (dist[u] + matrix[u][v] < dist[v]) { dist[v] = dist[u] + matrix[u][v]; path[v] = u; if (!visited[v]) { Q.push(v); enqueue_num[v]++; if (enqueue_num[v] >= vertex_num) return false; visited[v] = 1; } } } } } return true; } void Print() { for (int i = 0; i < vertex_num; i++) { if (i != source) { cout << source << " 到 " << i << " 的最短距離是:" << dist[i] << ",路徑是:" << i; int t = path[i]; while (t != source) { cout << "--" << t; t = path[t]; } cout << "--" << source << endl; } } } int main() { cout << "請輸入圖的頂點數,邊數,源點:"; cin >> vertex_num >> edge_num >> source; for (int i = 0; i < vertex_num; i++) for (int j = 0; j < vertex_num; j++) matrix[i][j] = (i != j) ? INT_MAX : 0; // 初始化 matrix 數組 cout << "請輸入 " << edge_num << " 條邊的信息:\n"; int u, v, w; for (int i = 0; i < edge_num; i++) { cin >> u >> v >> w; matrix[u][v] = w; } if (SPFA()) Print(); else cout << "存在負權迴路!\n"; return 0; }
運行以下:算法
/* Test 1 */ 請輸入圖的頂點數,邊數,源點:5 7 0 請輸入 7 條邊的信息: 0 1 100 0 2 30 0 4 10 2 1 60 2 3 60 3 1 10 4 3 50 0 到 1 的最短距離是:70,路徑是:1--3--4--0 0 到 2 的最短距離是:30,路徑是:2--0 0 到 3 的最短距離是:60,路徑是:3--4--0 0 到 4 的最短距離是:10,路徑是:4--0 /* Test 2 */ 請輸入圖的頂點數,邊數,源點:4 6 0 請輸入 6 條邊的信息: 0 1 20 0 2 5 3 0 -200 1 3 4 3 1 4 2 3 2 存在負權迴路!
若是某個點進入隊列的次數大於等於 n,則存在負權迴路。爲何恰恰是 n?數組
對於一個不存在負權迴路的圖,設其頂點數爲 n,咱們把圖稍微「轉換」下,以下圖 A:性能
其中 k≤n-1,當 k=n-1 時,即爲上圖 B。優化
每操做完一個批次的點,至少有一個點的最短路徑被肯定。這裏讀者只需從 Dijkstra 算法方面來考慮便可。Dijkstra 每次循環都找出dist[]
裏的最小值,能夠對應到這裏的每一個批次。spa
一個不存在負權迴路的圖,最多有 n-1 個批次,每作完一個批次至少有一個點的最短路徑被肯定,即一個點的入隊次數不超過 n-1。由於若一個頂點要入隊列,則必存在一條權值之和更小的路徑,而在最多作完 n-1 個批次後,全部頂點的最短路徑都被肯定。(這裏須要注意的是,若是一個批次中,有多條路徑對某頂點進行更新,則該頂點只會被入隊一次,這從代碼就能夠看出)code
對於一個不存在負權迴路的圖,咱們假設其頂點數爲 n,邊數爲 m。htm
引自 SPFA 論文:考慮一個隨機圖,運用均攤分析的思想,每一個點的平均出度爲 $O(\frac m n)$,而每一個點的平均入隊次數爲 2,所以時間複雜度爲 $O(n⋅\frac m n⋅2)=O(2m)=O(m)$。
關於上述的「平均入隊次數爲 2」,2 這個數字從何得來,我也找不到證實,從網上各位朋友對此的一致態度:尚待商榷。可是能夠肯定的是,SPFA 算法在隨機圖中的平均性能是優於 Bellman_Ford 算法的。
SPFA 的最佳時間複雜度爲 $O(n)$。好比上圖 B,每一個點只入隊一次。
接着再看下 SPFA 的最差時間複雜度,它發生在一個徹底圖中,以下圖(爲突出重點,其他邊未畫出),
咱們約定,0 點爲源點,每次更新完 k 點出隊後,k+1 點均可以再次對 k 點進行更新併入隊,其中。1≤ k≤ n-2 那麼咱們得出:
0 點,入隊 1 次;
1 點,入隊 n-1 次;
2 點,入隊 n-2 次;
3 點,入隊 n-3 次;
.
n-2 點,入隊 2 次;
n-1 點,入隊 1 次;
由於是徹底圖,因此每一個點的出度爲 n-1,所以總的時間複雜度爲:
$$ (n-1)⋅[1+1+2+3+...+(n-2)+(n-1)]=O(n^3) $$
因爲是徹底圖,也能夠表達成 $O(nm)$。很容易看出,SPFA 算法的時間複雜度很不穩定。