首页 > 其他分享 >我的无优化 SPFA 果然有问题

我的无优化 SPFA 果然有问题

时间:2023-09-20 19:33:19浏览次数:40  
标签:果然 结点 队列 入队 SPFA 算法 优化

SPFA 是一个非常好用的最短路径算法,可以跑负边权、判负环。但总有一些良心出题人要卡掉它。

我是 SPFA 小姐的狗!

可爱的 SPFA 娘

“单源最短路啊?人家最擅长啦!”

10 mins later

“唔……应该是这样……然后这样……诶诶怎么还没有处理完啦!这个数据好讨厌的QAQ”

“话说这个数据没有负权边为什么不叫小迪来啊欺负我吗QAQ”

又是5分钟过去

“啊终于处理完啦!”SPFA站起身,满意地伸了个懒腰

“唔……好像花的时间很长呢……希望主人不要因此抛弃我……”

如果她还有点智能的话,她就会发现她做了很久的无用功

但是……她根本不能确定哪些事是无用的啊

转载自洛谷的帖子

前置知识

先复习一下,不然大家可能已经忘记算法原理了。

Bellman–Ford 算法:不断尝试对图上每条边进行松弛。每进行一轮循环,就对图上所有的边都尝试进行一次松弛操作,当一次循环中没有成功的松弛操作时,算法停止。

假设最短路存在,那么一次松弛操作会使得最短路的边数至少加 \(1\)。又因为最短路的边数最多为 \(n-1\),那么最多执行 \(n-1\) 轮松弛操作,总时间复杂度为 \(O(nm)\)。

Bellman–Ford 算法显然是会遍历很多没有必要再去遍历的边,所以它的复杂度不优。

显然,只有上一次被松弛的点所连接的边才有可能被松弛,那么我们就可以把被松弛的点放入队列,只去遍历队列中的点所连的边,这就是 SPFA 算法的原理。

显然,它的时间复杂度是 \(O(kE)\),\(E\) 为边数,\(k\) 为每个点平均入队次数,一般有 \(k \leq 2\)。

简单优化

1. SLF 优化(Small Label First)

把普通的队列变成双端队列,对于即将入队的元素 \(x\),将其与队首元素的 \(\operatorname{dis}_x\) 进行比较,若队首元素较大,则从队首插入;否则从队尾插入。

2. LLL 优化(Large Label Last)

也是使用双端队列,维护当前队列中元素到起点距离的平均值,设这个平均值为 \(ave\),若 \(\operatorname{dis}_x>ave\),从队尾插入;否则从队首插入。

意义:可以让更新出结点最终解可能性更大的结点优先进行更新,减少了迭代次数。

较高级优化

1. SLF 优化带容错

定义一个容错值 \(diff\),当 \(\operatorname{dis}_x\) 大于队首元素的 \(\operatorname{dis}+diff\) 时,从队尾插入;否则从队首插入。

意义:可以使程序不陷入局部最优解。

2. mcfx 优化

定义一个区间 \(\left[ l,r \right]\)(通常取 \(l = 2,r=\sqrt{V}\)),当入队结点的入队次数属于这个区间时,从队首插入;否则从队尾插入。

意义:网格图表现优秀。假设有一个结点,从这个结点出发的大多数边都只能更新一个非最优解,那么它会导致迭代过多,那么经过 mcfx 优化可以使得它在队列中的优先级降低。一般可以与带容错的 SLF 优化一同使用。

3. Swap-SLF

若队列发生改变,并且有队首元素的 \(\operatorname{dis}\) 大于队尾元素的 \(\operatorname{dis}\),那么交换队首和队尾元素。

意义:比普通的 SLF 优化快很多,可以让队列更加接近优先队列,非常难卡。

4. 基于 DFS 的优化

原本的 SPFA 算法使用广度优先搜索,对于扩展的新结点,总是放到队列末尾,这样会中断迭代的连续性。那我们采用深度优先搜索的思想,不断从新结点向下递归求解。

这样判负环会更简单些,只用一个辅助数组记录当前结点是否在递归栈中即可。

但是在拓扑关系较强时仍是广度优先搜索更优一些,所以考虑进行优化。

可以考虑在进行第一次 DFS 时,对扩展结点采取一些限制,以快速求得一个最优解,然后再进行第二次 DFS。

但这样仍然不如 BFS,BFS 的优势在于某个点在出队之前可能再次被更新,而得到了更优解进行下一次迭代,而 DFS 常常会用一个劣解进行深层的迭代。

那么可以迭代加深搜索,结合前面的限制方法,通过限制结点递归的深度并逐步放宽限制进行求解。

有两种比较好的限制方法:

  1. 依次设置深度上限为 \(1,2,4,\dots,2^k\);
  2. 依次设置深度上限为 \(20,30,40,50,60,80,100,\dots\)

实验表明,在网格图中,限制深度的 DFS 效果大幅优于 BFS。

5. NTR 优化

维护一个叫作临时最短路树的东西,刚开始只有一个起点。

在 SPFA 的过程中,每次松弛边 \((u,v)\) 时将 \(v\) 的父亲设为 \(u\)。

\(v\) 可能存在后代,所以将其所有后代的 vis 标记清除;在 SPFA 过程中如果发现队首元素的 vis 为 \(0\) 就跳过。

因为如果我们能松弛边 \((u,v)\),那么从边 \((u,v)\) 走一定比以前那种走法更优。

这里的描述参考自叫做 Raffica 的博主,他将这个研究结果发了论文并将论文发给了 Andrew.V.Goldberg 教授。Andrew.V.Goldberg 教授说这不是 Tarjan 算法吗?(以 Tarjan 命名的算法有几十种)

似乎出自 Tarjan 未完全公开发表的论文,感兴趣可以发 E-mail 和 Tarjan 要。

6. RMF 优化(Reconfiguration-Minimum-First)

设定一个阈值 \(k\),并规定每个点的入队次数不能大于 \(k\)。

按这样跑完一遍 SPFA 之后,把所有点到起点的距离从小到大排序并依次加入队列(无法到达的不加),再跑一遍没有限制的 SPFA,会跑的飞快。

可以调整参数或者多跑几遍(类似模拟退火)。

7. UNVRS 优化

考虑 Swap-SLF 优化,如果队尾比队首小进行交换。那么我们可以考虑将队首后面若干个和队尾前面若干个也进行交换。

玄学优化

1. 边序随机

将读入的边随机打乱,之后再进行 SPFA。

2. 队列随机

每个结点入队时,以 \(\dfrac{1}{2}\) 的概率分别进入队首和队尾。

3. 队列随机优化

每若干次入队后,将队列元素随机打乱。

5. 猴排优化

每次取队首时,随机选一些数,然后取出其中最小的。

数据结构优化

通过更换使用的数据结构,一般是优先队列或者 ZKW 线段树。

在正边权上,使用这两种数据结构的 SPFA 与 Dijkstra 相当;负边权上可以会被卡成指数级别。

参考资料

  1. OI-Wiki 最短路页面
  2. spfa 的魔改方法
  3. SPFA 算法的玄学方法
  4. 知乎-如何看待 SPFA 已死这种说法
  5. 迭代求解的利器——SPFA 算法的优化与应用-姜碧野
  6. NTR 优化-Raffica
  7. 复活 SPFA

标签:果然,结点,队列,入队,SPFA,算法,优化
From: https://www.cnblogs.com/baijian0212/p/spfa.html

相关文章

  • 谷歌优化里的cache: 搜索运算符
    cache: 运算符是可用于查找网页的缓存版本的搜索运算符。Google会生成缓存版本,以便在网站无法访问的情况下,用户仍可访问网页。cache: 运算符只能用于网页搜索。虽然Google缓存的目标受众群体是Google搜索用户,但它对网站创建者和开发者了解Google在将网页编入索引时看到的......
  • 谷歌优化里的图片搜索运算符
    与网页搜索类似,Google图片也支持专用的搜索运算符,即 src: 和 imagesize:。这些运算符仅适用于Google图片;它们对其他Google产品和服务不起作用。src: 搜索运算符src: 搜索运算符将返回在 src 属性中引用了运算符中提供的图片网址的网页。例如:src:https://example.com/me......
  • 凸优化
    凸集对于点集\(C\),如果\(\forallx,y\inC\)满足以\(x,y\)为端点的线段都落在\(C\)内,就称\(C\)为凸集。以\(x,y\)为端点的线段写成方程的形式是\(u=x+\theta(y-x)\),\(\theta\in[0,1]\)。因此“线段落在\(C\)内”这一条件可以写作“\(\thetax+(1-\theta)y\inC\),\(\theta\in......
  • 关闭MyEclipse7.0自动更新 + 优化启动
    关闭MyEclipse7.0自动更新+优化启动1.window-->preferences-->General-->StartupandShutdown-->在列表中找到"AutomaticUpdatesScheduler"项去掉前面的勾。2.Window-->Preferences-->MyeclipseEnterpriseWorkbench-->Maven4Myeclipse-->......
  • 从 5s 到 0.5s!CompletableFuture 异步任务优化技巧,确实优雅!
    一个接口可能需要调用N个其他服务的接口,这在项目开发中还是挺常见的。举个例子:用户请求获取订单信息,可能需要调用用户信息、商品详情、物流信息、商品推荐等接口,最后再汇总数据统一返回。如果是串行(按顺序依次执行每个任务)执行的话,接口的响应速度会非常慢。考虑到这些接口之间......
  • mysql大数据量 分页查询优化
    最近我老表问我一个面试问题,如果数据量很大,分页查询怎么优化。个人觉得无非就是sql优化,那无非就是走索引,避免回表查询(覆盖索引,也就是不要用select *  ,走主键索引,叶子节点有保存了数据),减少回表查询次数(定位到非聚簇索引树的叶子节点少,小表驱动大表等)我下面自己测了一个500......
  • MySQL常规优化操作
    查询SQL语句执行频率查询mysql服务启动时长SHOWSTATUSLIKE'uptime';下列输出表示服务启动了276324秒+---------------+--------+|Variable_name|Value|+---------------+--------+|Uptime|276324|+---------------+--------+查询全局SQL执行的频......
  • 使用策略模式优化你的代码
    策略模式简介策略模式(StrategyPattern:Defineafamilyofalgorithms,encapsulateeachone,andmaketheminterchangeable.)中文解释为:定义一组算法,然后将这些算法封装起来,以便它们之间可以互换,属于一种对象行为型模式。总的来说策略模式是一种比较简单的模式,听起来可能有点费......
  • 希尔排序:优化的插入排序
    希尔排序(ShellSort)是一种插入排序的改进算法,它通过比较相距一定间隔的元素进行排序,逐步减小间隔,最终实现整体有序。本文将详细介绍希尔排序的工作原理,提供示例和Python、Go、Java以及C语言的实现代码。希尔排序的基本思想希尔排序的核心思想是将整个待排序序列分割成若干个子序列,......
  • Xor-Subsequence (字典树优化DP)
     思路;明显的是,后一个i要从前面一个进行更新, 利用dpeasy版本ai<=200,发现当n>=300时,对他是没有影响的,这样比较好记录ans进行更新,利用数据结构处理hard版本拆位,利用字典树dp,把参数变成相同的参数,a[i]和i,(比大小:前K位一样第K+1位不一样......