简介
即 Shortest Path Faster Algorithm。
一种基于松弛(relax)操作的最短路算法。
支持负权。
能找到某个结点出发到所有结点的最短路,或者报告某些最短路不存在。
不过呢:
很多时候我们并不需要那么多无用的松弛操作。
很显然,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。
那么我们用队列来维护“哪些结点可能会引起松弛操作”,就能只访问必要的边了。
提醒
虽然在大多数情况下 SPFA 跑得很快,但其最坏情况下的时间复杂度为 (operatorname{O}(nm)),将其卡到这个复杂度也是不难的,所以考试时要谨慎使用(在没有负权边时最好使用 Dijkstra 算法,在有负权边且题目中的图没有特殊性质时,若 SPFA 是标算的一部分,题目不应当给出 Bellman-Ford 算法无法通过的数据范围)。
除了队列优化(SPFA)之外,Bellman-Ford 还有其他形式的优化,这些优化在部分图上效果明显,但在某些特殊图上,最坏复杂度可能达到指数级。
- 堆优化:将队列换成堆,与 Dijkstra 的区别是允许一个点多次入队。在有负权边的图可能被卡成指数级复杂度。
- 栈优化:将队列换成栈(即将原来的 BFS 过程变成 DFS),在寻找负环时可能具有更高效率,但最坏时间复杂度仍然为指数级。
- LLL 优化:将普通队列换成双端队列,每次将入队结点距离和队内距离平均值比较,如果更大则插入至队尾,否则插入队首。
- SLF 优化:将普通队列换成双端队列,每次将入队结点距离和队首比较,如果更大则插入至队尾,否则插入队首。
更多优化以及针对这些优化的 Hack 方法,可以看 fstqwq 在知乎上的回答 。
Raffica 在知乎上的回答 提到了一种很难被卡的 SPFA,大家可以看看
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=5e5+5;
struct edge {
int to,nxt,w;
} e[M];
int cnt,head[N];
int add(int u,int v,int w) {
e[++cnt].to=v,e[cnt].w=w,e[cnt].nxt=head[u],head[u]=cnt;
return cnt;
}
deque<int> q;
bitset<N> in;
int dis[N];
int main() {
int n,m,s;
scanf("%d %d %d",&n,&m,&s);
for(int i=1,u,v,w; i<=m; i++) {
scanf("%d %d %d",&u,&v,&w);
if(u==v) {
continue;
}
add(u,v,w);
}
for(int i=1; i<=n; i++) {
dis[i]=INT_MAX;
}
q.push_front(s),in[s]=1,dis[s]=0;
while(q.size()) {
int u=q.front();
q.pop_front(),in[u]=0;
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].to,w=e[i].w;
if(dis[u]+w<dis[v]) {
dis[v]=dis[u]+w;
if(!in[v]) {
if(dis[v]>dis[q.front()]) {
q.push_back(v);
} else {
q.push_front(v);
}
in[v]=1;
}
}
}
}
for(int i=1; i<s; i++) {
printf("%d ",dis[i]);
}
printf("0 ");
for(int i=s+1; i<=n; i++) {
printf("%d ",dis[i]);
}
return 0;
}
版权说明
部分来源于 OI-Wiki。