求最短路径之Bellman-Ford算法

       Bellman-ford算法是求含负权图的单源最短路径算法,效率很低,但代码很容易写。即进行不停地松弛(原文是这么写的,为什么要叫松弛,争议很大),每次松弛把每条边都更新一下,若n-1次松弛后还能更新,则说明图中有负环,无法得出结果,否则就成功完成。Bellman-ford算法有一个小优化:每次松弛先设一个旗帜flag,初值为FALSE,若有边更新则赋值为TRUE,最终如果还是FALSE则直接成功退出。Bellman-ford算法浪费了许多时间做无比要的松弛,所以SPFA算法用队列进行了优化,效果十分显著,高效难以想象。SPFA还有SLF,LLL,滚动数组等优化。

       Dijkstra算法中不允许边的权是负权,如果遇到负权,则可以采用Bellman-Ford算法。   

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

       适用条件&范围 :

       1.单源最短路径(从源点s到其它所有顶点v);    

       2.有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);   

       3.边权可正可负(如有负权回路输出错误提示);   

       4.差分约束系统;   

      之所以需要第3部分的原因,是因为,如果存在从源点可达的权为负的回路。则 应为无法收敛而导致不能求出最短路径。
考虑如下的图:

《求最短路径之Bellman-Ford算法》
 

     经过第一次遍历后,点B的值变为5,点C的值变为8,这时,注意权重为-10的边,这条边的存在,导致点A的值变为-2。(8+ -10=-2)

《求最短路径之Bellman-Ford算法》

       第二次遍历后,点B的值变为3,点C变为6,点A变为-4。正是因为有一条负边在回路中,导致每次遍历后,各个点的值不断变小。
       再回过来看一下bellman-ford算法的第三部分,遍历所有边,检查是否存在d(v) > d (u) + w(u,v)。因为第二部分循环的次数是定长的,所以如果存在无法收敛的情况,则肯定能够在第三部分中检查出来。比如

《求最短路径之Bellman-Ford算法》
 

       此时,点A的值为-2,点B的值为5,边AB的权重为5,5 > -2 + 5. 检查出来这条边没有收敛。
 
       所以,Bellman-Ford算法可以解决图中有权为负数的边的单源最短路径问。

       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 tru 

        描述性证明:

       首先指出,图的任意一条最短路径既不能包含负权回路,也不会包含正权回路,因此它最多包含|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又可以松弛为-5,d[c]又可以松弛为-7.下一个周期,d[a]又可以更新为更小的值,这个过程永远不会终止。因此,在迭代求解最短路径阶段结束后,可以通过检验边集E的每条边(u,v)是否满足关系式 d[v]> d[u]+ w(u,v) 来判断是否存在负权回路。

       示例代码:

#include <iostream>

usingnamespace std;

constint maxnum=100;

constintmaxint =99999;

 

// 边,/

typedefstruct Edge{

    int u, v;    //起点,重点

    intweight;  //边的权值

}Edge;

 

Edge edge[maxnum];     //保存边的值

int  dist[maxnum];     //结点到源点最小距离

 

intnodenum, edgenum, source;    // 结点数,边数,源点

 

// 初始化图

void init()

{

    // 输入结点数,边数,源点

    cin>>nodenum >> edgenum >> source;

    for(int i=1; i<=nodenum;++i)

        dist[i]= maxint;

    dist[source]=0;

    for(int i=1; i<=edgenum;++i)

    {

        cin>> edge[i].u>> edge[i].v>> edge[i].weight;

        if(edge[i].u==source)          //注意这里设置初始情况

            dist[edge[i].v]= edge[i].weight;

    }

}

 

// 松弛计算

voidrelax(int u, int v, int weight)

{

    if(dist[v]> dist[u]+ weight)

        dist[v]= dist[u]+ weight;

}

 

boolBellman_Ford()

{

    for(int i=1; i<=nodenum1;++i)

        for(int j=1; j<=edgenum;++j)

            relax(edge[j].u, edge[j].v, edge[j].weight);

    bool flag=1;

    // 判断是否有负环路

    for(int i=1; i<=edgenum;++i)

        if(dist[edge[i].v]> dist[edge[i].u]+ edge[i].weight)

        {

            flag =0;

            break;

        }

    return flag;

}

int main()

{

    //freopen(“input3.txt”, “r”, stdin);

    init();

    if(Bellman_Ford())

        for(int i =1;i <=nodenum; i++)

            cout<< dist[i]<< endl;

    return0;

}

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/johnpub/article/details/8633596
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞