1、前提引入ios
咱們學過了Bellman-Ford算法,如今又要提出這個SPFA算法,爲何呢?算法
考慮一個隨機圖(點和邊隨機生成),除了已肯定最短路的頂點與還沒有肯定最短路的頂點之間的邊,其它的邊所作的都是無用的,大體描述爲下圖(分割線以左爲已肯定最短路的頂點):數組
其中紅色部分爲所作無用的邊,藍色部分爲實際有用的邊。既然只需用到中間藍色部分的邊,那就是SPFA算法的優點之處了。優化
2、算法描述spa
算法特色:在 Bellman-ford 算法的基礎上加上一個隊列優化,減小了冗餘的鬆弛操做,是一種高效的最短路算法。code
時間複雜度:O(mn)blog
關鍵詞:初始化 鬆弛操做 隊列隊列
主要變量以下:ci
int n 表示有n個點,從1~n標號it
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沒法處理帶負環的圖。
3、代碼實現
#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; }
運行以下: