SPFA算法

Posted 陶无语

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SPFA算法相关的知识,希望对你有一定的参考价值。

  SPFA全称Shortest path faster algorithm算法,用于在一个带权图中搜索单源最短路径。SPFA是基于Bellman-Ford算法的,只是在原有的基础上做了优化,但是时间复杂度是没有改变的,还是O(VE)。

  在图G(V,E)上运行Bellman-Ford算法,需要执行总共V次循环,每次循环需要依据所有E中的边对相连顶点进行松弛,即对于边(u,v),令v.d=min(u.d+(u,v).d,v,d)。无论图本身性质如何,Bellman-Ford算法的性能都不会受到丝毫影响,Bellman-Ford算法只于图的规模有关。但是SPFA算法在此基础上做了优化,对于一些具有特殊性质的图,能获得非常大的性能提升,而对于一般图,性能相较Bellman-Ford算法一般依旧有一定优势,且在所有可以应用Bellman-Ford算法的场景中SPFA都可以应用,因此SPFA成了Bellman-Ford的一个优秀替代品。当然你可能会问,既然两种算法的时间复杂度都达到了O(VE),那为什么不使用时间复杂度为O(Elog2V)的Dijkstra算法来解决寻找单源路径的问题呢?原因有二,其一是Dijkstra算法实现复杂,尤其还需要搞一个最小堆来配合,其二是Dijkstra算法无法应对图中有负权边的情况。其中第二个原因是最主要的原因,毕竟像最大流,最小费用最大流等引入残存网络的问题,其中不可避免就会出现负权边,而这时候Dijkstra就失去了用武之地。

  说了这么多,下面说一下SPFA的具体流程。首先我们要维护一个队列,初始时将源顶点加入队列中,并为每个顶点添加一个是否处于队列的标记。之后不断从队列中弹出头部顶点,沿着出边去不断松弛其它顶点,一旦一个顶点得到松弛,就将其到队列尾部。实际上这里队列是用于保存所有可能可以通过松弛操作导致其余顶点最短距离改变的顶点,即所有不在队列中的顶点,我们无法借助其去松弛其余顶点。这样我们就可以不必向Bellman-Ford算法中一样像无头苍蝇一样利用所有的边进行松弛,而可以精确地利用队列中的顶点。

queue = empty-queue
queue.addLast(src)
while(queue.isEmtpy() == false)
    head = queue.removeFirst()
    head.inQueue = false
    for e in head.out //循环所有以head的出边
        if(e.dst.d > head.d + e.d)
            e.dst = head.d + e.d
            if(e.dst.inQueue == false)
                queue.addLast(e.dst)
                e.dst.inQueue = true

  在图中不存在负环路的情况下,SPFA由于减少了对许多没有意义的边的松弛,因此性能会远高于Bellman-Ford算法。但是在图中存在负环路的场景下,Bellman-Ford算法和SPFA都运行的相当缓慢,因为必须通过一定的循环次数才能保证图中存在负环路,而这些循环次数无论是运行在Bellman-Ford还是SPFA中都相差不大。对于存在负环路的情况,可以使用一个简单的优化方案,来大幅减少循环的次数。我们为每个顶点赋予一个累加器,用于记录该顶点进入队列的次数,当我们发现一个顶点进入队列中的次数达到|V|的时候,就意味着图中存在负环路。稍微说明一下原因,很容易可以发现SPFA算法和广度优先搜索算法非常类似,因此我们可以这样理解,在我们搜索了以src为中心半径为1的圆内所有顶点后,我们找到了所有以src为源长度为1的最短路径。之后一直到我们搜索了以src为中心半径为|V|-1的圆内所有顶点后,我们实际上找到了所有以src为源长度不超过|V|-1的最短路径。在每次半径扩大1时,每个顶点最多入队一次(inQueue保证只入队一次)。而当我们发现一个顶点入队达到了|V|次时,这意味着我们利用了src与顶点之间的最短路径长度超过了|V|-1,而在无负权环路的情况下这是不可能的。

以上是关于SPFA算法的主要内容,如果未能解决你的问题,请参考以下文章

SPFA算法以及负环判断模板

SPFA最短路算法

算法描述》关于SPFA和Dijkstra算法的两三事

题目1008:最短路径问题(SPFA算法)

最短路径——SPFA算法(C++)

hdoj2544 最短路(Dijkstra || Floyd || SPFA)