SPFA 的优化


SPFA 优化

众所周知,SPFA 它死了

但有些时候你会嫌支持负边的 dijkstra 麻烦,于是不得不选择 SPFA

那么,你需要 SPFA 优化!

基础篇

如果你是只想看代码的小萌新,请看这里。

否则可以直接跳过这一篇。

SLF 优化

我们可以参考一下 dijkstra 的思路。

dijkstra 每次取队列中的最小值,减少了同一节点入队次数和更新 dis 次数,于是避免了死亡

那么我们感性理解一下,如果我们在 SPFA 中每次也取较小 dis 的点是不是也可以得到一定的优化呢?

我们可以通过双端队列,在节点入队时实现这一点:把需要入队的点的 dis 与队首的比较一下,如果小于队首则将节点从队首入队,否则从队尾入队。

LLL 优化

其实本质原理与 SLF 一样,只是实现方式不同啦 ~

同样使用双端队列,我们额外维护一下队列中所有点的 dis 的平均值,把即将入队的点与这个平均值比较,决定从队首还是队尾入队。

贴(伪)代码

显而易见,这两个优化并不相矛盾,所以我们可以同时使用!

int dis[N];
//起点是 S
inline void SPFA(int S)
{
    //下面这三个东西只在该函数中使用,于是把它们放入函数里,避免被外界调用,不易重名、出错
	static deque<int> q;//双端队列
	static bool inque[N];//记录每个点是否在队列中
	static int sum = 0;//因为平均数不方便维护,我们选择维护队列中 dis 的和
	//TODO: 此处应初始化 dis 数组为 inf
	dis[S] = 0;
	q.push_back(S);
	while (q.size()) {
		int x = q.front();
		q.pop_front();
		inque[x] = 0, sum -= dis[x];//出队维护qaq
		for (/*TODO: 遍历 x 的所有出边: 出边 e 和 对应点 y  */) {
			if (dis[y] > dis[x] + w[e]) {
				dis[y] = dis[x] + w[e];
				if (!inque[y]) {
//此处为两种优化的核心
                    //一定要注意队列为空的特殊情况
					if (q.empty() || dis[y] < dis[q.front()] || dis[y]*q.size() < sum)
						q.push_front(y);
					else q.push_back(y);
//
					inque[y] = 1, sum += dis[y];//入队记得维护qwq
				}
			}
		}
	}
}

十分简单好写呢,为什么不随手写一个呢

提高篇

这一篇没有代码。

看这里的话,前面的东西是非必须的。

SLF 优化

每次入队时,与队首比较,若大于队首则插入队尾,反之插入队首。

HACK:链套菊花图

LLL 优化

每次入队时,与队内平均值比较,若大于队首则插入队尾,反之插入队首。

HACK:起点连一条巨大边权的边,使得队内均值一直很大。

SLF 带容错

每次入队时,与队首比较,若比队首大超过一定阈值则插入队尾,反之插入队首。

HACK:开大边权,总和超过 \(10^{12}\) 时优化失效显著。

SLF + swap

出队时,若队首大于队尾,则交换首尾。

HACK:链套菊花图 + 外挂诱导节点

mcfs 优化

在第 \([L,R]\) 次访问这个节点时,将其队首入队,否则队尾入队。

HACK:网格图表现优异,菊花图表现差劲。

P.S. 此优化加上SLF 带容错 效果显著提升

总结

所有的 SPFA 优化都是在试图使队列更接近优先队列,所以 SPFA 永远只能是一些情况下接近 dijsktra ,而在特定图中被卡回 \(O(MN)\) 的原形。

参考:https://www.zhihu.com/question/292283275


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM