SPFA算法分析:
SPFA算法实际上是“队列优化的Bellman-Ford 算法”。
SPFA 算法的流程如下:
- 1.建立一个队列,最初队列中只含有起点
1
。 - 2.取出队头节点
x
,扫描它的所有出边(x,y,z)
,若dist[y] > dist[x]+z
,则使用dist[x]+z
更新dist[y]
。同时,若y
不在队列中,则把y
入队。 - 3.重复上述步骤,直到队列为空。
在任意时刻,该算法的队列都保存了待扩展的节点。每次入队相当于完成一次 dist
数组的更新操作,使其满足三角形不等式。
一个节点可能会入队、出队多次。最终,图中节点收敛到全部满足三角形不等式的状态。
这个队列 避免了Bellman-Ford算法中对不需要扩展的节点的冗余扫描,在随机图上运行效率为 O(km)
级别,其中 k
是一个较小的常数。
注意,在特殊构造的图上,该算法很可能退化为O(nm)
,必须谨慎使用。(这就是“关于SPFA,它已经死了”的由来)
————————————————————————
ps:
即使图中存在负权边,Bellman-Ford和 SPFA算法也能够正常工作(这很强大)。
另外,如果图中不存在长度为负数的边,那么类似于优先队列 BFS,我们也可以使用priority_queue
对SPFA算法进行优化,用堆代替一般的队列,用于保存待扩展的节点,每次取出“当前距离最小”的节点(堆顶)进行扩展,节点第一次从堆中被取出时,就得到了该点的最短路。这与堆优化 Dijkstra算法的流程完全一致。
“堆优化的 Dijkstra算法” 和 “优先队列优化的SPFA 算法”两种思想殊途同归,都得到了 非负权图 上 时间复杂度为O(mlogn)
的单源最短路径算法。
代码片段:
int n; // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储每个点到1号点的最短距离
bool st[N]; // 存储每个点是否在队列中
// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则输出“impossible”
void spfa()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int> q;
q.push(1);
st[1]=true;//st数组存储当前这个点是否在队列当中,防止队列当中存储重复的点
while(!q.empty())
{
int t=q.front();
q.pop();//队头t弹出了,置为false,表示不在队列中
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int b=e[i];
if(dist[b]>dist[t]+w[i])
{
dist[b]=dist[t]+w[i];
if(!st[b])//如果更新的点b不在队列中,才把它加到队列中去,如果队列中已存在b,则不需要将b重复插入
{
q.push(b);
st[b]=true;
}
}
}
}
if(dist[n]==inf) cout<<"impossible"<<endl;
else cout<<dist[n]<<endl;
}
例题:AcWing 851. spfa求最短路
时间复杂度:
平均情况下 O(m),最坏情况下 O(nm),
(n
表示点数,m
表示边数)
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 150000+10;
#define inf 0x3f3f3f3f
int h[N],e[N],ne[N],w[N],idx;
int dist[N];
bool st[N];
int x,y,z,n,m;
void add(int x,int y,int z)
{
e[idx]=y,ne[idx]=h[x],h[x]=idx,w[idx]=z,idx++;
}
void spfa()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int> q;
q.push(1);
st[1]=true;//st数组存储当前这个点是否在队列当中,防止队列当中存储重复的点
while(!q.empty())
{
int t=q.front();
q.pop();//队头t弹出了,置为false,表示不在队列中
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int b=e[i];
if(dist[b]>dist[t]+w[i])
{
dist[b]=dist[t]+w[i];
if(!st[b])//如果更新的点b不在队列中,才把它加到队列中去
{
q.push(b);
st[b]=true;
}
}
}
}
if(dist[n]==inf) cout<<"impossible"<<endl;
else cout<<dist[n]<<endl;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
memset(h,-1,sizeof h);
cin>>n>>m;
while (m -- )
{
cin>>x>>y>>z;
add(x,y,z);
}
spfa();
return 0;
}