最短路
本文介绍4种算法:SPFA, Dijkstra, Bellman-Ford, Floyd
PS:文中分大小写。 图为G(V,E),V为节点集合,E为边集合,但v表示某个节点(v∈V)
Dijkstra:
- 原理:通过每一次解压节点min{key[v]|v∈G-已经生成的最短路径的树}并添加到树中,并将所有以v为起点的边松弛(key域),来达到最短路(贪心)
- 注意:图中不能有负权,不能查是否有负权回路
- 步骤:
- 初始化key域。每个key[v]=INF,v∈V
- 将最短路起点S的key[S] = 0
- 定义一个优先队列 priority_queue (#include <queue>) ,并将S节点(可以考虑用pair来绑定key域,也可以用一个结构来绑定)压入。//STL的队列默认为最大优先,故要创建一个比较类,重载()来实现最小堆
- 循环(直到队列为空)每一次解压最小值, 将最小key域所绑定的节点v解压出来并添加到树中(直接用一个bool数组来确定是否在树中即可,要输出路径就定义一个父亲域并维护)将v为起点的边全部松弛一遍,维护key域。并将这条边的终点压入队列。
- 分析:时间复杂度O(N^2)
- 适用于:稠密图
- 优化:优化堆的实现,即手打FIB堆或其它比STL快的堆= =。可以达到O(NlogN)
Bellman-Ford:
- 原理:通过V-1次(最短路长度最多有V-1条边嘛- -)对每一条边松弛,来实现最短路(同样是贪心)
- 好处:图中可以有负权,也能判断是否有负权回路
- 步骤:
- 初始化key域。每个key[v]=INF,v∈V
- 循环(1~n-1)
- 枚举所有边进行松弛
- 判断是否有回路,循环(1~V)
如果以v∈V为起点的边还能松弛,即有回路。(自己拿算导看证明哈,或者谷歌百度,或者自己想)
- 分析:时间复杂度O(VE)
- 适用于:稀疏图和负权图
- 优化:我布吉岛
Floyd:
4行代码:
for(k = 1; k <= N; k++) for(i = 1; i <= N; i++) for(j = 1; j <= N; j++) node[i][j] = min(node[i][j], node[i][k]+node[k][j]);
SPFA:(全名为:ShortestPathFasterAlgorithm)
看到好文章,直接转过来了(点击打开链接)
我们知道,求一个图中的单源最短路径有Dijkstra、Ford这两种经典的算法。其复杂度均是O(N2),通过堆的优化,Dijkstra可以达到O(NlogN)的复杂度。但对于一些十分稀疏的图,这个复杂度就有些浪费了。当一个图中的边数e远远小于N2时,我们就考虑是否有一种算法,其时间复杂度只跟边数e有关系。于是SPFA诞生了。
我们知道,Dijkstra的O(N2)是因为又一重循环是为了找到目前的Dist值最小的节点,于是我们想,能不能不找最小的,只是按照一定的顺序更新就可以了。那么算法的雏形就出来了,设S为源点:
Quene←S While Quene not empty do Tmp←Quene For each node i with an edge to Tmp do If Dist[i] > Dist[Tmp]+length(edge(i,Tmp)) then Dist[i]←Dist[Tmp]+length(edge(i,Tmp)) If i not in the Quene then Quene←i
这就是传说中的SPFA。我们来看看它的执行过程:
为了图片的美观,我隐藏了顶点的标号。左上角的顶点为源点,黄的的点表示在队列中,绿色的点表示正在处理:
至此,队列空,算法结束。
为什么它能收敛呢?为什么它不会一直循环下去呢?因为总有一刻Dist[i]已经达到最小值,这时i这个顶点就再也不会入队了,并且当访问到i的时候,与i邻接的顶点也会被更新。所以总有一刻,所有的Dist都是最优值,算法结束。
可以证明,假设每个节点的平均入队次数是k,那么时间复杂度就是O(ke)。而这里的k几乎可以认为是一个常数,所以简算之后时间复杂度为O(e)。
如果时间复杂度优于这个值,就是说我们并没有处理完图中所有的边,就不可能一定可以得到最优解,所以,O(e)是所期望的最好的复杂度。
PS: 有可能会更新,,欢迎大家吐槽
博客地址:www.cnblogs.com/iwtwiioi 本文为博主原创文章,未经博主允许不得转载。一经发现,必将追究法律责任。