infinity

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  36 随笔 :: 0 文章 :: 25 评论 :: 0 Trackbacks
转自:http://hi.baidu.com/jzlikewei/blog/item/94db7950f96f995a1038c2cd.html

Bellman-Ford 算法及其优化

Bellman-Ford算法与另一个非常著名的Dijkstra算法一样,用于求解单源点最短路径问题。Bellman-ford算法除了可求解边权均非负的问题外,还可以解决存在负权边的问题(意义是什么,好好思考),而Dijkstra算法只能处理边权非负的问题,因此 Bellman-Ford算法的适用面要广泛一些。但是,原始的Bellman-Ford算法时间复杂度为 OVE,Dijkstra算法的时间复杂度高,所以常常被众多的大学算法教科书所忽略,就连经典的《算法导论》也只介绍了基本的Bellman-Ford算法,在国内常见的基本信息学奥赛教材中也均未提及,因此该算法的知名度与被掌握度都不如Dijkstra算法。事实上,有多种形式的Bellman-Ford算法的优化实现。这些优化实现在时间效率上得到相当提升,例如近一两年被热捧的SPFAShortest-Path Faster Algoithm 更快的最短路径算法)算法的时间效率甚至由于Dijkstra算法,因此成为信息学奥赛选手经常讨论的话题。然而,限于资料匮乏,有关Bellman-Ford算法的诸多问题常常困扰奥赛选手。如:该算法值得掌握么?怎样用编程语言具体实现?有哪些优化?与SPFA算法有关系么?本文试图对Bellman-Ford算法做一个比较全面的介绍。给出几种实现程序,从理论和实测两方面分析他们的时间复杂度,供大家在备战省选和后续的noi时参考。

Bellman-Ford算法思想

Bellman-Ford算法能在更普遍的情况下(存在负权边)解决单源点最短路径问题。对于给定的带权(有向或无向)图 G=V,E),其源点为s,加权函数 w 边集 E 的映射。对图G运行Bellman-Ford算法的结果是一个布尔值,表明图中是否存在着一个从源点s可达的负权回路。若不存在这样的回路,算法将给出从源点s G的任意顶点v的最短路径d[v]

Bellman-Ford算法流程分为三个阶段:

(1)    初始化:将除源点外的所有顶点的最短距离估计值 d[v] ←+∞, d[s] ←0;

(2)    迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)

(3)    检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在 d[v]中。

算法描述如下:

Bellman-Ford(G,w,s) boolean   //G ,边集 函数 w s为源点

1        for each vertex v ∈ V(G) do        //初始化 1阶段

2            d[v] ←+∞

3        d[s] ←0;                             //1阶段结束

4        for i=1 to |v|-1 do               //2阶段开始,双重循环。

5           for each edge(u,v) ∈E(G) do //边集数组要用到,穷举每条边。

6              If d[v]> d[u]+ w(u,v) then      //松弛判断

7                 d[v]=d[u]+w(u,v)               //松弛操作   2阶段结束

8        for each edge(u,v) ∈E(G) do

9            If d[v]> d[u]+ w(u,v) then

10            Exit false

11    Exit true

下面给出描述性证明:

   首先指出,图的任意一条最短路径既不能包含负权回路,也不会包含正权回路,因此它最多包含|v|-1条边。

   其次,从源点s可达的所有顶点如果 存在最短路径,则这些最短路径构成一个以s为根的最短路径树。Bellman-Ford算法的迭代松弛操作,实际上就是按顶点距离s的层次,逐层生成这棵最短路径树的过程。

在对每条边进行1遍松弛的时候,生成了从s出发,层次至多为1的那些树枝。也就是说,找到了与s至多有1条边相联的那些顶点的最短路径;对每条边进行第2遍松弛的时候,生成了第2层次的树枝,就是说找到了经过2条边相连的那些顶点的最短路径……。因为最短路径最多只包含|v|-1 条边,所以,只需要循环|v|-1 次。

每实施一次松弛操作,最短路径树上就会有一层顶点达到其最短距离,此后这层顶点的最短距离值就会一直保持不变,不再受后续松弛操作的影响。(但是,每次还要判断松弛,这里浪费了大量的时间,怎么优化?单纯的优化是否可行?)

如果没有负权回路,由于最短路径树的高度最多只能是|v|-1,所以最多经过|v|-1遍松弛操作后,所有从s可达的顶点必将求出最短距离。如果 d[v]仍保持 +∞,则表明从s到v不可达。

如果有负权回路,那么第 |v|-1 遍松弛操作仍然会成功,这时,负权回路上的顶点不会收敛。

 

 

 

例如对于上图,边上方框中的数字代表权值,顶点A,B,C之间存在负权回路。S是源点,顶点中数字表示运行Bellman-Ford算法后各点的最短距离估计值。

此时d[a]的值为1,大于d[c]+w(c,a)的值-2,由此d[a]可以松弛为-2,然后d[b]又可以松弛为-5,d[c]又可以松弛为-7.下一个周期,d[a]又可以更新为更小的值,这个过程永远不会终止。因此,在迭代求解最短路径阶段结束后,可以通过检验边集E的每条边(u,v)是否满足关系式 d[v]> d[u]+ w(u,v) 来判断是否存在负权回路。

posted on 2008-11-11 17:46 infinity 阅读(8378) 评论(11)  编辑 收藏 引用 所属分类: acm

评论

# re: Bellman-Ford算法 2008-11-14 21:09 新手
终于明白为什么了~
怎么优化?  回复  更多评论
  

# re: Bellman-Ford算法 2008-11-14 21:43 infinity
@新手
bellman-ford的优化是通过队列实现的,也就是通常所说的spfa(Shortest Path Faster Algorithm)
具体操作方法是建一个队列queue[],用一个数组dist[]表示各点到起点的距离,用一个数组vis[]表示某点是否在队列里面,然后循以下步骤:
1:将起点入队列。
2:取队首元素u,对u的每一邻接点v实施松弛操作:即如果dist[u]+w[u][v]<dist[v](能被更新),则dist[v]=dist[u]+w[u][v],如果v不再队列中,就将点v入队列。
3:如果队列空则算法结束,否则继续2一直到队列空为止。
时间复杂度大概是O(ke), 其中k为所有顶点进队的平均次数,k一般小于等于2。

还有就是,有时候有些题,不用队列而用栈可能会更快!  回复  更多评论
  

# re: Bellman-Ford算法 2009-12-24 22:26 笑傲江湖
写得非常好,详细又有条理!  回复  更多评论
  

# re: Bellman-Ford算法 2010-12-03 16:10 Ted
谢谢 很清楚,,  回复  更多评论
  

# re: Bellman-Ford算法 2011-03-04 16:21 tjt
谢谢~  回复  更多评论
  

# re: Bellman-Ford算法 2011-03-19 23:03 heroming
太感谢了!很详细易懂。  回复  更多评论
  

# re: Bellman-Ford算法 2011-08-12 18:27 acmer
太好了 找了这么多就看到这一个讲的最具体 最详细  回复  更多评论
  

# re: Bellman-Ford算法 2011-10-20 19:20 may
写得很好,很详细,很清楚。感谢!!!!!!  回复  更多评论
  

# re: Bellman-Ford算法[未登录] 2013-04-23 16:27 可乐
“此后这层顶点的最短距离值就会一直保持不变”这句怎么理解呢?最短路径不是到了最后才能确定吗  回复  更多评论
  

# re: Bellman-Ford算法 2013-08-26 22:42 路过
@可乐
同求- -#  回复  更多评论
  

# re: Bellman-Ford算法 2014-02-24 20:33 ACalvin
@路过
个人理解:前面提到是至多只有s层的节点的最短距离,即已经搜索完全源点到它的所有路径,因此可以这么理解  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理