zoukankan      html  css  js  c++  java
  • 【转】Bellman_ford算法

    原文链接:http://www.cnblogs.com/Jason-Damon/archive/2012/04/21/2460850.html

    摘自百度百科    

              Bellman-ford算法是求含负权的单源最短路径算法,效率很低,但代码很容易写。即进行不停地松弛(relaxation),每次松弛把每条边都更新一下,若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.差分约束系统;

     

      Bellman-Ford算法描述:

     

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

     

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

     

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

     

      描述性证明:

     

      首先指出,图的任意一条最短路径既不能包含负权回路,也不会包含正权回路,因此它最多包含|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 遍松弛操作仍然会成功,这时,负权回路上的顶点不会收敛。

    C++ pseudo code

    复制代码
    Bellman-Ford(G,w,s) :boolean //图G ,边集 函数 w ,s为源点   
    for each vertex v ∈ V(G) do //初始化 1阶段   
              d[ v] ←+∞ 
     d[s] ←0; //1阶段结束 
     for i=1 to |v|-1 do //2阶段开始,双重循环。
        for each edge(u,v) ∈E(G) do //边集数组要用到,穷举每条边。           If d[v]> d[u]+ w(u,v) then //松弛判断,w(w,v)是u到v的权值
                 d[v]=d[u]+w(u,v) //松弛操作 2阶段结束 
     for each edge(u,v) ∈E(G) do
       If d[v]> d[u]+ w(u,v) then
             Exit false   //存在负权回路
     Exit true 
    复制代码

     负权回路

     在一个图里每条边都有一个权值(有正有负)
    如果存在一个环(从某个点出发又回到自己的路径),而且这个环上所有权值之和是负数,那这就是一个负权环,也叫负权回路
    存在负权回路的图是不能求两点间最短路的,因为只要在负权回路上不断兜圈子,所得的最短路长度可以任意小。(转自百度知道)

     代码实现:

      Bellman-ford算法的运行时间为O(VE),V为顶点数,E为边数。

    复制代码
    /*************************************************************************
        > File Name: Bellman_ford.cpp
        > Author: He Xingjie
        > Mail: gxmshxj@163.com
        > Created Time: 2014年06月08日 星期日 22时33分07秒
        > Description: 
     ************************************************************************/
    #include<iostream>
    #include<cstdio>
    
    using namespace std;
    
    #define INF 99999
    int map[100][100], dist[100];
    
    bool Bellman_ford(int n, int s)
    {
        int v, u;
    
        for (v=1; v<n; v++)
        {
            if (map[s][v] == INF)
                dist[v] = INF;
            else
                dist[v] = map[s][v];
        }
    
        dist[s] = 0;
    
        for (v=1; v<n; v++)
            for (u=0; u<n; u++)
                if (map[u][v] < INF)  //u->v has path
                    if (dist[v] > dist[u] + map[u][v])
                        dist[v] = dist[u] + map[u][v];
    
        //遍历所有的边
        for (v=0; v<n; v++)
            for (u=0; u<n; u++)
                if (v != u && map[u][v] != INF)
                    if (dist[v] > dist[u] + map[v][u])
                        return false;
    
        return true;
    }
    
    void PrintMap(int n)
    {
        int i, j;
        //输出矩阵
        for (i=0; i<n; i++)
        {
            for (j=0; j<n; j++)
            {
                if (map[i][j] == INF)
                    printf("INF ");
                else
                    printf("%d  ", map[i][j]);
            }
    
            printf("
    ");
        }
    }
    
    void PrintShortestValue(int n)
    {
        int i;
    
        for (i=1; i<n; i++)
            printf("%d:%d ", i, dist[i]);
        printf("
    ");
    }
    
    int main()
    {
        int n, m, i, j;
    
        freopen("input.txt", "r", stdin);
        cin>>n>>m;    //n是顶点数,m是边数
    
        //初始化
        for (i=0; i<n; i++)
        {
            for (j=0; j<n; j++)
                map[i][j] = INF;
        }
    
        //输入
        for(int i=1; i<=m; i++)
        {
            int i,j;
            cin>>i>>j;
            cin>>map[i][j];
        }
    
        PrintMap(n);
    
        Bellman_ford(n, 0);
        PrintShortestValue(n);
    
        return 0;
    }
    复制代码
  • 相关阅读:
    Linnia学习记录
    漫漫考研路
    ENS的学习记录
    KnockoutJS 3.X API 第四章 数据绑定(4) 控制流with绑定
    KnockoutJS 3.X API 第四章 数据绑定(3) 控制流if绑定和ifnot绑定
    KnockoutJS 3.X API 第四章 数据绑定(2) 控制流foreach绑定
    KnockoutJS 3.X API 第四章 数据绑定(1) 文本及样式绑定
    KnockoutJS 3.X API 第三章 计算监控属性(5) 参考手册
    KnockoutJS 3.X API 第三章 计算监控属性(4)Pure computed observables
    KnockoutJS 3.X API 第三章 计算监控属性(3) KO如何实现依赖追踪
  • 原文地址:https://www.cnblogs.com/yanqi0124/p/4609070.html
Copyright © 2011-2022 走看看