单源最短路深度分析
一.前言:
1.定义:
E:边的数量
V:点的数量
S:源点
稠密图:E=θ(V*V)
稀疏图:E=θ(V)
圈:代表从自身出去至少一个点再回到自身(eg:v->a->...->v)
环:代表有自身到自身的路线(eg:v->v)。
2.此分析,Map(地图)采用邻接表存储,邻接矩阵空间时间复杂度均上升一个数量级。
3.所有点到源点的距离用数组存储,记为Di(意为i点到源点S距离)。
4.此分析主要分析SPFA、Dijkstra和拓扑排序在各种情况下的选择问题,不考虑Bellman-ford算法(实在太坑爹)。
5.不考虑负圈(更本不存在最短路。。。),亦不考虑环。
PS:拓扑排序在最短路方面的主要功能为以O(E+V)的时间判断有无圈圈,在此使用是因为在无圈且出发点为源点的情况下其性能远超Dijkstra和SPFA。
PS:不要迷信SPFA在任何时候都快,说它是最快的算法是因为SPFA可以根据各种情况作出调整,使其成为最快。
二.Dijkstra算法简介:
1.算法流程:
初始化所有点到源点S的距离为无穷大,初始化源点S到源点S的距离为0。
在Map中找到所有点距离源点S最近的未被锁定(代表此点并未在之前被使用)的节点K,用K更新所有与K相连的节点并锁定K(代表K被使用过),知道所有点都被锁定,算法结束。
2.时间复杂度:
O(V*V+E),第一个V代表总共要锁定V个点,第二个V代表要找到距离源点S最近的点需要V次(也就是扫过所有的点)。并此算法保证每条边只用一次。
3.空间复杂度:
O(Max(V,E))
4.优点:
编程快,如果采用邻接矩阵存Map,则只需短短几行。
5.缺点:
无法处理带负权的边
三.SPFA算法简介
1.算法流程:
此算法需要队列。
初始化所有点到源点S的距离为无穷大,初始化源点S到源点S的距离为0,将源点S进队。
如果队列非空,则将队首元素(节点K)出队,更新与K相连的所有节点ai(0<=i<=与K相连的节点数),如果ai被更新并且不在队列中,则将ai入队。直到队列为空,算法结束。
2.时间复杂度:
O(T*V+T*E),此算法不保证每个点只出队列一次,所以T代表每个点最多出队列的次数(不大于V,一般情况T都是常数级)。因每个点多次出队,所以每条边就不止使用一次,
3.空间复杂度:
O(Max(V,E))
4.优点:
BFS经典算法;
可处理带负边的各种图;
可找到负圈,如果节点K的出队次数大于V,则必然存在负圈
5.缺点:
在稠密图中,尤其是带负权的图中,T很可能会退化到V,使其时间复杂度上升一个数量级;不带负权的图中,T也可能发生严重退化。
四.拓扑排序算法简介(仅能在无圈且出发点为源点的情况下使用)
1.算法流程
此算法需要队列。
此算法需要维护一个入度数组Ri,代表第i个节点有几条入边(比如v->w,则w有一个入边v,Rw至少为1)。
初始化所有点到源点S的距离为无穷大,初始化源点S到源点S的距离为0,初始化所有点的入边数Ri,将Ri为0的节点加入队列。
如果队列非空,则将队首元素(节点K)出队,更新与K相连的所有节点ai(0<=i<=与K相连的节点数),在Map上删去K点以及K的出边,同时Ri减1(不用显式的删除K和K的出边,Ri减1就相当于删除操作),如果Ri==0,则将节点i加入队列。直到队列为空,算法结束。
2.时间复杂度:
O(V+E),此算法保证每个节点、每条边使用一次。
4.空间复杂度:
O(Max(V,E))
5.缺点:
无法处理带圈图;
无法处理当源点非起始点之一的图(或者删除所有源点S到不了的点及其出边)。
五.SPFA算法优化:
为什么只优化SPFA算法?因为SPFA算法可以优化出Dijkstra和拓扑排序,甚至更好。
1.SLF优化:
设队首元素为i,队列中要加入的元素为j,当Dj<=Di时加到队首,反之加到队尾。
虽不改变时间下界,但经验证明能提速30%以上。
2.LLL优化(极不常用,在此仅介绍):
维护队列中所有点到源点的平均值d,设队首元素为i,每次出队时,当Di>d时,将队首元素移至队尾,否则出队。
3.优先队列的普通实现:
入队O(1),出队O(V)。
此优化使SPFA在Dijkstra算法占优的图中具有和Dijkstra算法同等时间下界(但速度绝对比Dijkstra快)。(原理:在不含负边的图中,从优先队列中出去的点再也不会进入队列,这和Dijkstra的锁点效果一样)
4.优先队列的堆实现:
入队O(lgV),出队O(lgV).
在此推荐使用配对堆。
如果使用二叉堆,需要考虑当更新优先队列中的节点i的最短距离时,需要调整节点i在二叉堆中的位置。
5.加上入度数组的优化:
更新节点i后,当i的入度为0时再入队(呃,好吧,思路一样,其实就是拓扑排序……)
六.算法选择:
1.无圈图:
a) 源点是出发点之一:
i. 拓扑排序:O(V+E)=O(E)
ii. SPFA+入度数组:O(V+E)=O(E)
b) 其余与有圈图一样。
2.有圈图:
a) 稠密图:
i. 无负边:
1. Dijkstra:O(V*V+E)=O(E)。
2. SPFA+优先队列的普通实现+SLF优化(推荐):O(V*V+E)=O(E)。
ii. 有负边:
1. SPFA+优先队列的普通实现+SLF优化:O(T*V*V+T*E)=O(V*E),T退化到V。
b) 稀疏图:
i. 无负边:
1. SPFA+优先队列的堆实现:O(V*lgV+E*lgV)=O(E*lgV)。
ii. 有负边:
1. SPFA+优先队列的堆实现:O(T*V*lgV+T*E*lgV)=O(E*lgV),T保持常数级。