昨天刚刚学习了西南交大段凡丁教授提出的Bellman-ford改进版---SPFA算法。从名字就可以看出该算法应该很快。从昨天做的一道题:n=100W,E=8*N的图,跑SPFA只花了700MS,可以证实该算法效率确实很高。
当初学算法导论上的Bellman-ford的时候就觉得这个算法有点暴力,做了很多冗余计算。举例来看,有图为:v0->v1->v2->v3(每边权值为1),Bellman-ford要做3次循环,假定每次循环按(v2,v3),(v1,v2),(v0,v1)的顺序松弛,实际效果是每次循环只改变了一个点的估计,算法总共要做9次松弛。而事实上,从v0出发,只要松弛3次就可以完成这个任务。
上述算法冗余的地方在于:用了未被优化的点来松弛别的边。比如前两次循环中,d[v2]均为inf,那么用v2来松弛别的边是毫无意义的。
而段教授的SPFA的优点在于,每次只用被优化过的点来松弛别的边。
算法框架如下:
SPFA(G,w,s)
1. INITIALIZE-SINGLE-SOURCE(G,s)
2. INITIALIZE-QUEUE(Q)
3. ENQUEUE(Q,s)
4. While Not EMPTY(Q)
5. Do u<-DLQUEUE(Q)
6. For 每条边(u,v) in E[G]
7. Do tmp<-d[v]
8. Relax(u,v,w)
9. If (d[v] < tmp) and (v不在Q中) //这一步保证了用来松弛别的边的点都是被优化过的!
10. ENQUEUE(Q,v)
算法正确性的证明可以用到路径松弛性质(详见算法导论P361):
如果p=< v0,v1,…,vk > 是从s=v0 到 vk 的最短路径, 而且p的边按照(v0,v1)(v1,v2),…,(v(k-1),vk) 的顺序进行松弛,那么k[vk] = dis(s,vk)。这个性质的保持并不受其他松弛操作的影响,即使它们与p的边上的松弛操作混合在一起也是一样的。
假定没有负权环。
证明如下:
设p为v0->vk的最短路径中节点数最少的一条,设按顺序松弛到(vi-1,vi),那么vi必在队列中,否则与"最短路径"或"节点数最少"矛盾,所以接下来松弛的边为(vi,vi+1)。这样循环不变式在初始条件成立,并一直维持到算法结束。证毕。
如果某个节点入队次数大于N,必定存在负权环。这点小弟不会证明,望路过的大牛不吝赐教。
SPFA算法的复杂度为O(kE),k为平均入队次数。段教授的论文中说明,经过大量的实验,k在通常情况下都在2以下。
相比较Bellman-ford的O(VE)是个巨大的改进。
而dijkstra用二叉堆实现复杂度为(ElogV),用斐波那契堆为(VlogV),因此dijkstra可能在边多的情况下理论上更快一点。但考虑到编程的复杂性,具有O(E)复杂度的SPFA是个相当优异的算法。