刷题的本意不在于刷了多少题,而在于通过刷题理解了多少算法,理解有多深……
Bellman-Ford算法与Dijkstra算法类似,也是用于求单源点的最短路径,与Dijkstra算法不同的是,Dijkstra算法只能用于求非负权值有向图(无向图可以看成是有向图)的单源点最短路径,而Bellman-Ford算法可以求含负权的单源点有向图的最短路径,而且Bellman-Ford算法还可以用于判断图中是否存在负权回路(负权回路即是图中存在一个环,环上的所有权值之和是负数,那么这个环就是负权回路)。但存在负权回路的图是不能够求最短路径的,因为求最短路径时会在负权回路上不断地兜圈子,所有的最短路径长度会任意小。
Bellman-Ford算法的实现很简单,但理解起来并不容易,比较复杂。原理是进行n-1次的松弛操作(n是结点个数),每次松弛操作时从边的角度出发,更新每个结点到源点的距离,使得每个结点到源点距离逐步逼近最短距离,一共要进行n-1次的松弛操作。
对于存在最短距离的有向图来说,任意一条最短路径都不可能存在正权回路,也不可能存在负权回路,因为最短路径最多只包含n-1条边,其次从源点可达的所有顶点如果存在最短路径,则这些最短路径构成以源点s为根的最短路径树,Bellman-Ford算法的松弛过程本质上就是按顶点距离源点s的层次,逐次生成这棵最短路径树的过程。
对于权值均为正的有向图(无向图转化为有向图)而言,进行完第一次松弛操作,就是找到了层次最多为1的那些树枝,即找到了与s最多有一条边相连的那些顶点的最短路径,进行完第二次的松弛操作后,就找到了层次最多为2的那些树枝,即找到了与s经过两条边的那些顶点的最短路径,………因为最短路径最多只包含n-1条边,所以进行n-1次的松弛操作就可以。每实施一次松弛操作就会有一层的顶点找到最短路径,且这些顶点的最短路径长度会保持不变,在以后的松弛操作中不会受影响。
对于有负权的无负权回路的有向图(无向图转化为有向图)则不然,进行完第i次松弛操作不一定就找到了第i层结点的最短路径,与边的顺序有关。
对于存在负权回路的图来说,是不能找到最短路径的,进行完n-1次的松弛操作后还能更新,因为寻在最短路径时会不断地在负权回路上兜圈子,所有的最短路径长度会任意小。如果不能理解可以借助三个有向图来理解:
第一个图 起点 终点 权值 1 2 -2 , 2 5 5 , 4 5 2 , 2 3 -2 , 3 4 3 , 4 2 2 , 1 4 3 ;第二个图 起点 终点 权值 1 2 -2 , 2 5 5 , 4 5 2 , 2 3 -2 , 3 4 -3 , 4 2 2 , 1 4 3 ; 第三个图 起点 终点 权值 1 2 2 , 2 5 5 , 4 5 2 , 2 3 2 , 3 4 3 , 4 2 2 , 1 4 3 ;
第一个图不含负权回路,但含负权,第二个图含负权回路,第三个图权值均为正,源点都是1,分析完这三个图,就能够很好地理解Bellman-Ford算法。
分析完这三个图就会发现Bellman-Ford算法浪费了大量的时间在做没必要的松弛操作,所以算法效率很低,需要进行优化。其中有一个小的优化就是每次松弛时设置以标志flag,初值为false,因为可能没有进行第n-1次松弛操作时,顶点到源点的距离就不能更新了(即已经找到了所有结点的路径),如果下次松弛时仍要更新到源点的最短距离,则值flag的值为true,进行完这次松弛操作后,如果flag的值为false,则直接退出,已经找到了所有结点的最短路径长度。还有其他优化方法,如SPFA等,效率很高。
Bellman-Ford算法实现代码:x[i],y[i],w[i]分别表示第i条边的起点、终点、权值,d[i]表示顶点i到源点最短距离,m条边,n个顶点。
for(i=1;i<n;i++)
for(j=1;j<=m;j++)
if(d[x[j]]+w[j]<d[y[j]])
d[y[j]]=d[x[j]]+w[j];
for(i=1;i<=m;i++)
if(d[x[i]]+w[i]<d[y[i]])
return false;
else
return true;