SPFA 介绍

简介

即 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

原文地址:https://www.cnblogs.com/Sam2007/p/13903831.html