一、前提引入
我們學過了Bellman-Ford算法,現在又要提出這個SPFA算法,為什么呢?
考慮一個隨機圖(點和邊隨機生成),除了已確定最短路的頂點與尚未確定最短路的頂點之間的邊,其它的邊所做的都是無用的,大致描述為下圖(分割線以左為已確定最短路的頂點):
其中紅色部分為所做無用的邊,藍色部分為實際有用的邊。既然只需用到中間藍色部分的邊,那就是SPFA算法的優勢之處了。
二、算法描述
算法特點:在 Bellman-ford 算法的基礎上加上一個隊列優化,減少了冗余的松弛操作,是一種高效的最短路算法。
時間復雜度:O(mn)
關鍵詞:初始化 松弛操作 隊列
主要變量如下:
int n 表示有n個點,從1~n標號
int s,t s為源點,t為終點
int dis[N] dis[i]表示源點s到點i的最短路徑
int pre[N] 記錄路徑,pre[i]表示i的前驅結點
bool vis[N] vis[i]=true表示點i在隊列中
queue<int> q 隊列,在整個算法中有頂點入隊了要記得標記vis數組,有頂點出隊了記得消除那個標記
【初始化】
dis數組全部賦值為INF,pre數組全部賦值為-1(表示還不知道前驅),
dis[s] = 0 表示源點不要求最短路徑(或者最短路徑就是0)。
【隊列+松弛操作】
讀取隊頭頂點u,並將隊頭頂點u出隊(記得消除標記);將與點u相連的所有點v進行松弛操作,如果能更新估計值(即令d[v]變小),那么就更新,另外,如果點v沒有在隊列中,那么要將點v入隊(記得標記),如果已經在隊列中了,那么就不用入隊,這樣不斷從隊列中取出頂點來進行松弛操作。
以此循環,直到隊空為止就完成了單源最短路的求解。
【算法過程】
設立一個隊列用來保存待優化的頂點,優化時每次取出隊首頂點 u,並且用 u 點當前的最短路徑估計值dis[u]
對與 u 點鄰接的頂點 v 進行松弛操作,如果 v 點的最短路徑估計值dis[v]
可以更小,且 v 點不在當前的隊列中,就將 v 點放入隊尾。這樣不斷從隊列中取出頂點來進行松弛操作,直至隊列空為止。
【檢測負權回路】
方法:如果某個點進入隊列的次數大於等於 n,則存在負權回路,其中 n 為圖的頂點數。
說明:SPFA無法處理帶負環的圖。
三、代碼實現
#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) { int p = i; stack<int> s; cout << "頂點 " << source << " 到頂點 " << p << " 的最短路徑是: "; while (source != p) //路徑順序是逆向的,所以先保存到棧 { s.push(p); p = path[p]; } cout << source; while (!s.empty()) //依次從棧中取出的才是正序路徑 { cout << "--" << s.top(); s.pop(); } cout << " 最短路徑長度是:" << dist[i] << 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] = INT_MAX; //初始化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 << "Sorry,it have negative circle!\n"; return 0; }
運行如下: