一、前提引入
我們學過了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;
}
運行如下:

