算法简介

SPFA(Shortest Path Faster Algorithm)是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。也有人说SPFA本来就是Bellman-Ford算法,现在广为流传的Bellman-Ford算法实际上是山寨版。

 

 

求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm。 
从名字我们就可以看出,这种算法在效率上一定有过人之处。 
很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。 
简洁起见,我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点。 
和上文一样,我们用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且 v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。 
定理3 只要最短路径存在,上述SPFA算法必定能求出最小值。 
证明:每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越 小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束, 这时的最短路径估计值就是对应结点的最短路径值。(证毕) 


刚才我们只是笼统地说SPFA算法在效率上有过人之处,那么到底它的复杂度是怎样的? 
定理4 在平均情况下,SPFA算法的期望时间复杂度为O(E)。 
证明:上述算法每次取出队首结点u,并访问u的所有临结点的复杂度为O(d),其中d为点u的出度。运用均摊分析的思想,对于|V|个点|E|条 边的图,点的平均出度为 ,所以每处理一个点的复杂度为O( )。假设结点入队的次数h,显然h随图的不同而不同。但它仅与边的权值分布有关。我们设 h=kV,则算法SPFA的时间复杂度为 。在平均的情况下,可以将k看成一个比较小的常数,所以SPFA算法在一般情况下的时间复杂度为O(E)。(证 毕) 
聪明的读者一定发现了,SPFA和经过简单优化的Bellman-Ford无论在思想上还是在复杂度上都有相似之处。确实如此。两者的思想都属 于标号修正的范畴。算法是迭代式的,最短路径的估计值都是临时的。算法思想是不断地逼近最优解,只在最后一步才确定想要的结果。但是他们实现的方式上存在 差异。正因为如此,它们的时间复杂度其实有较大差异的。在Bellman-Ford算法中,要是某个点的最短路径估计值更新了,那么我们必须对所有边指向的终点再做一次松弛操作;在SPFA算法中,某个点的最短路径估计值更新,只有以该点为起点的边指向的终点需要再做一次松弛操作。在极端情况下,后者的效率将是前者的n倍,一般情况下,后者的效率也比前者高出不少。基于两者在思想上的相似,可以这样说,SPFA算法其实是Bellman-Ford算法的一 个进一步优化的版本。

算法流程

算法大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。

这个算法,简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法

SPFA——Shortest Path Faster Algorithm,它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。SPFA的实现甚至比Dijkstra或者Bellman_Ford还要简单:

设Dist代表S到I点的当前最短距离,Fa代表S到I的当前最短路径中I点之前的一个点的编号。开始时Dist全部为+∞,只有Dist[S]=0,Fa全部为0。

维护一个队列,里面存放所有需要进行迭代的点。初始时队列中只有一个点S。用一个布尔数组记录每个点是否处在队列中。

每次迭代,取出队头的点v,依次枚举从v出发的边v->u,设边的长度为len,判断Dist[v]+len是否小于 Dist[u],若小于则改进Dist[u],将Fa[u]记为v,并且由于S到u的最短距离变小了,有可能u可以改进其它的点,所以若u不在队列中,就 将它放入队尾。这样一直迭代下去直到队列变空,也就是S到所有的最短距离都确定下来,结束算法。若一个点入队次数超过n,则有负权环

SPFA 在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也 就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。设一个点用来作为迭代点对其它点进行改进的平均次数为k,有办法证明对于通常的情况,k在2左右

算法代码


Begin
   initialize
-single-source(G,s);
   initialize
-queue(Q);
   enqueue(Q,s);
   
while not empty(Q) do begin
      u:
=dequeue(Q);
      
for each v∈adj[u] do begin
         tmp:
=d[v];
         relax(u,v);
         
if (tmp<>d[v]) and (not v in Q) then enqueue(Q,v);
         end;
      end;
End;

 


procedure spfa;
begin
fillchar(q,
sizeof(q),0); h:=0; t:=0;//队列
fillchar(v,sizeof(v),false);//v[i]判断i是否在队列中
for i:=1 to n do dist[i]:=maxint;//初始化最小值
 
inc(t); q[t]:
=1; v[1]:=true;
dist[
1]:=0;//这里把1作为源点
 
while not(h=t) do
begin
    h:
=(h+1)mod n;x:=q[h];  v[x]:=false;
    
for i:=1 to n do
     
if (cost[x,i]>0)and(dist[x]+cost[x,i]<dist[i]) then
     begin
       dist[i]:
=dist[x]+cost[x,i];
       
if not(v[i]) then
       begin
         t:
=(t+1)mod n; q[t]:=i;v[i]:=true;
       end;
     end;
end;
end;

 

 下面举一个实例来说明SFFA算法是怎样进行的:

 

    设有一个有向图G{VE},其中,V{V0,V1,V2,V3,V4}E{<V0,V1>,<V0,V4>,<V1,V2>,<V1,V4>,<V2,V3>,<V3,V4>,<V4,V2>}{2,10,3,7,4,5,6},见下图:

算法执行时各步的Queue队的值和D数组的值由下表所示。

表一  实例图SPFA算法执行的步骤及结果

初始

第一步

第二步

第三步

第四步

第五步

queue

D

queue

D

queue

D

queue

D

queue

D

queue

D

V0

0

V1

0

V4

0

V2

0

V3

0

 

0

 

V4

2

V2

2

 

2

 

2

 

2

 

 

 

5

 

5

 

5

 

5

 

 

 

 

 

9

 

9

 

 

10

 

9

 

9

 

9

 

9

算法执行到第五步后,队Queue空,算法结束。源点V0V1的最短路径为2,到V2的最短路径为5,到V3的最短路径为9,到V4的最短路径为9,结果显然是正确的。