Dijkstra Bellman-Ford SPFA 几种最短路径求法 基本理论

最短路问题
一、概念:
在图论中最短路径有着非常广泛的应用,而由于应用的方法不同也形成了多种求最短路径的方法,而对于不同类的问题虽然每种方法都可以用,不过各有其最优的实现方法。
首先对于最短路径问题,可以分成如下几类:


1. 求单源最短路径问题:这种问题是最短路径问题中最基本的问题了,其主要目的是求从一个固定源点开始到每一个点的最短路径;对于这个问题,所有的方法都适用;


2. 求单汇最短路径问题:这种问题的目标是求所有的点到某个确定汇点的最短路径,不难想到,对于求所有点到某个点的最短路径,就相当于求从这个点出发到其余点的最短路径,不过需要对每条边进行“反向”操作(类似于将竖直上抛运动转化为自由落体运动一样),这样同理于单源最短路问题,所有的方法都适用;


3. 求一个确定源点到一个确定汇点的最短路径问题:这个问题其实非常简单,通过分析不难发现,解决这个问题的最好方法其实还是和单源最短路一模一样的,到现在为止尚未发现比应用求单源最短路方法更好的解决这个问题的方法;


4. 求每对源汇点间最短路径问题:这个问题首先可以简化为从每个节点开始求一遍单源最短路,然后慢慢统计即可。不过这个方法的复杂度比较大,这样子就衍生出了一种特殊的方法来求解这个问题,不过在时间复杂度上要落后于多次单源最短路,这点是比较重要的。


综上所述,其实在最短路径问题中,最重要的其实就是如何求解单源最短路问题。


二、算法:
对于单源最短路问题,我们有两种最基本的方法:Dijkstra和Bellman-Ford,这两种方法可以说是从基本思想上就有差别,而在这两种方法的基础上又衍生出了一些更优秀的算法。
首先声明,在最短路径中绝对不能包含负权环,因为如果存在负权环的话总能在现有的最短路径中找出一个更优的。


1. Dijkstra算法:
这个算法利用了贪心的思想,每次取出一个当前最短路长度最小的一个节点,然后以此节点为中心对和它相连的所有点进行一遍松弛操作,直到将所有的点做完为止。
可以看出这种方法在松弛操作部分是相当迅捷的,而算法也十分简单,这个基本算法的缺点主要是体现在如何找出一个最短路径最小的节点上,如果使用最朴素的算法,这种方法的复杂度会非常的大;当然,Dijkstra算法的一个致命缺陷就是不但不能处理负权环,还不能处理负权!这点非常重要。


2. 堆优化的Dijkstra算法:
通过1种对Dijkstra算法的分析,我们知道了如何找到最短路径长度最小的点是决定这个算法速度的关键,而最朴素的算法在得到这个点时需要O(V)的复杂度才能达到目的。显然这是不好的,而如何在更小的甚至是O(1)的复杂度完成这件事呢?通过对堆的学习可以知道可以利用最小堆的性质来做这个任务。
堆的定义很简单,一棵完全二叉树,且堆的根节点为这个堆中所有节点的极值。
最小堆的定义是堆的根节点是这个堆中值最小的,而每个节点的左右子堆均是一个最小堆(除非它没有子节点)。
而只要去维护这个堆,让这个堆时刻保持最小堆的性质,这样子在查找最短路径最小的节点的时候就可以使用O(1)的复杂度完成任务。


3. Bellman-Ford算法:
由于Dijkstra算法存在不能处理负权的弊端,Bellman-Ford算法被开发了出来:这
SPYNGELION OI 专题总结:最短路问题 2010/11/9
2 / 8
个算法将Dijkstra算法使用到的三角不等式成为“松弛操作”,通过反复的松弛操作使得整个图中各边的路径长度不断地逼近其最短路径长,直至算法结束时每条边的路径长度均达到其最短路径长。这种算法有效地解决的负权问题,同时对于含负权环的问题,这种算法也可以输出是否存在负权环。


4. SPFA算法:
由于Bellman-Ford的复杂度相当的大,所以SPFA算法应运而生。这个算法几乎和Bellman-Ford算法相同,不过这个算法使用到了队列对需要处理的节点进行维护,而不是像Bellman-Ford算法一样不管这个节点需不需要维护都对其进行判断——这样大幅的缩减了算法的复杂度。不过由于队列可能会展开的非常大,所以最好使用循环队列维护这个队列。但是这个算法相比于Dijkstra算法来说不是很稳定。


5. Floyd-Warshall算法:
这个算法是应用于求每对源汇点间最短路径长度的特殊方法,其应用了动态规划的思想,所以非常好理解也非常好写,但其最大的弊病是复杂度过大且很难被优化。
综上所述,相对来说效率和可实现性比较好的就是堆优化的Dijkstra算法和循环队列实现的SPFA算法了,其他的方法在效率上都是比较差的。
对比各算法的时间复杂度,Dijkstra算法的朴素实现是O(V2+E),Dijkstra算法的最小堆实现时O(VlogV+E),Bellman-Ford算法是O(VE),SPFA算法是O(kE),Floyd-Warshall算法是O(V3)。可以看出在平均复杂度上,堆优化的Dijkstra算法和SPFA算法还是非常好的。
更进一步说,在求无负环可带负权的网络中可以使用Johnson算法,这个算法的基本思想是先利用Bellman-Ford算法进行一次最短路的求解过程,然后再利用网络流中的Edmonds-Karp算法进行一次标号,最后在运行一遍Dijkstra算法,在每一步都能做到完美的情况下,这个算法的复杂度可以优化到O(V2logV+E),不过这个算法在NOIP中相当罕见。一般用最基本的方法就可以了,没有必要这么发挥。


三、程序框架:


1. 朴素的Dijkstra算法:
这个应该是所有求最短路径的可行算法中编程复杂度最小的了,按照定义来说,显而易见,我们只需做下面的几步工作:
1) 找出当前最短路径长度最小的节点设为u,注意u节点不能再之前的松弛操作中被访问过;


2) 以u为中心,查找由u发出的所有的边,设这些边构成集合E。我们的工作就是对集合E中的所有的边进行松弛操作,当然松弛操作的一个大前提是这条彼边所指向的节点v未被访问过。
而Dijkstra算法就是将上面的两步循环执行|V|-1次即可。
对于这个算法下面我来做一些说明:
首先为什么要循环执行|V|-1次?显而易见,通过这么多次的循环可以把以每一个节点开始的边都能进行过一次松弛操作,而|V|-1次所没有做的那个节点因为它所能扩展出的所有的节点都曾经完成过松弛操作,所以再做这个节点就没有意义了。


其次我来说明一下为什么Dijkstra算法无法处理负权:显然如果现在已经从u1这个节点对和它相连的每一个节点进行了一次更新,那么如果和u1相连的点钟有一个点叫u2,然后u2和另一个同u1相连的节点v存在一个负权边的话,按理说应该再在u2到v这条边进行一次松弛操作才可以。不过由于Dijkstra算法的性质导致了如果当前所要扩展的节点已经被更新过了就不再更新这个节点,所以导致结果不对。而对于包含负权环的图跟这个道理差不多,而且显而易见这样子求出来的结果一定不是最优解(负权环的最短路径问题无解)。
SPYNGELION OI 专题总结:最短路问题 2010/11/9
3 / 8
另:这个朴素算法的时间复杂度是O(V2+E),主要的时间浪费在了找出当前最短路
径长度最小的节点中。


2. 堆优化的Dijkstra 算法:
这个算法的求最短路径部分其实和朴素算法一样,不过就是在找出当前最短路径长
度最小的节点的过程中进行了优化,将复杂度有效地降低为O(VlogV+E)。
利用最小堆的性质可以在O(1)的时间内得到当前最短路径长度最小的节点,同时可
以在O(logn)的时间内维持堆的性质。
另:这个算法的时间复杂度是O(VlogV+E),在OI 中是一种非常常用的最短路径算
法。


3. Bellman-Ford 算法:
这个算法是对Dijkstra 算法不能处理负权的改进,通过不断的进行松弛操作来逐渐
逼近真实最短路径值,达到了处理负权的目的,同时也可以跟俊三角形不等式
d[u]  d[v]c[u,v]来判断这个图中是否存在负权环(这个三角形不等式也是最
短路问题中松弛操作的基本原理)。
这个算法就是从|V|-1 个节点开始对所有边进行松弛。
为什么要从|V|-1 这么多个节点开始做呢?就是因为在一个图中每个节点最多有
|V|-1 个节点可以对其进行更新,这样子只要松弛|V|-1 就一定可以使得三角形不
等式对于整个图都可以取到等号。
另:这个算法的时间复杂度是O(VE),复杂度还是比较大的,不过在查分约束系统
中比较常用(因为要检测是否存在负权环),具体在OI 的简单最短路问题中这个算
法不太适用(复杂度太大)。


4. SPFA 算法:
SPFA 算法其实和Bellman-Ford 算法非常类似,不过这个算法相对于Bellman-Ford
算法来说使用了队列维护当前需要进行松弛操作的点,这样子避免了对一个已经达
到最短路径的部分继续松弛,减少了无用功。
这个算法的大致步骤为:
1) 从当前的队列中取出队头(出队);
2) 以队头为中心,扩展与其相连的所有的节点,对它们之间的边进行一遍松弛操
作;对于每一个扩展出来的点,如果其当前不再队列中(入队),则将这个点
加入队列,同时标识这个节点已经在队列中。
3) 将原先队头节点标识为不再队列中。
反复执行上面的操作直至队列为空。
这个算法相对于Bellman-Ford 算法来说最大的改进就是使用了队列,其主要目的就
是说如果当前的这个点和由它发出的边如果已经被松弛到最优解了,那么以后就没
有必要再做关于这个节点的松弛操作了,这样就可以极大的削减算法的时间复杂度。
同时这个算法也可以非常方便的检测是否存在负权环:如果一个节点已经重复入队
|V|次,那么以这个节点为中心一定存在负权回路。对于这个的解释其实和
Bellman-Ford 算法为什么要循环|V|-1 次是一样的。


5. Floyd-Warshall 算法:
这个算法只适用于求每对节点之间的最短路径长度,对于这个特例之外的情况一定
有某个单源最短路算法要优于这个方法;对于这个特例有可能有单源最短路算法的
改进实现可以和这个算法相媲美。不过这个算法的复杂度铁定是O(V3),时间复杂
度很难优化。
SPYNGELION OI 专题总结:最短路问题 2010/11/9
4 / 8
这个方法运用了DP的思想,如果我们现在要求从u到v的最短路径长度,那么这个问题可以确切转化为从u到t的最短路径长度和从t到v的最短路径长度和的最小值。
显而易见这个算法是O(V3)的时间复杂度。

你可能感兴趣的:(工作,算法,优化,扩展,任务,2010)