zoukankan      html  css  js  c++  java
  • Bellman-ford 算法详解

    昨天说的dijkstra固然很好用,但是却解决不了负权边,想要解决这个问题,就要用到Bellman-ford.

    我个人认为Bellman-Ford比dijkstra要好理解一些,还是先上数据(有向图):

    5 7
    1 2 8
    1 3 5
    2 3 -6
    5 4 -3 2 4 7 3 5 -2 4 5 -3

    在讲述开,先设几个数组:

    origin[i]表示编号为i这条边的起点编号,如origin[4]=2

    destination[i]表示编号为i这条边的终点编号,如origin[5]=5

    value[i]表示编号为i这条边的权值,如value[3]=-6

    dis[i],和昨天一样,源点到i号点的估计距离,经过不断更新会变成时机距离,就是答案。

    bellmanford的实际意义就是扫描一条边,看如果走这条边能不能使这条边的dis[destination[i]],变少,现在我来模拟一下:

    初始的dis:[0,∞,∞,∞,∞]

    首先从第一条边1 2 8开始,判断走这条边能不能使这条边的终点的dis变短,原本dis[2]=∞,而dis[1]=0,而这条边的权值:value[1]=8,0+8<∞所以将dis[2]更新成8.

    dis[0,8,∞,∞,∞]

    然后是第二条边,用刚才的方法将dis[3]从∞更新成5.

    dis[0,8,5,∞,∞]

    第三条2 3 -8,原本的dis[3]=5,如果走第三条边,则dis[3]=dis[2]+value[3]=8+(-6)=2<5,所以dis[3]更新成2.

    dis[0,8,2,∞,∞]

    以此类推,经过第一轮更新,dis数组如下:

    dis[0,8,2,15,0]

    但是第一次更新后,并不是最优解于是开始第二次更新。

    按照第一次更新的步骤一步一步来得到的答案是

    dis[0,8,2,-3,0]

    这便是最优解,但是问题来了,一般要更新多少次呢?

    n-1次。这样能保证更新出的一定是最优解。

    好了,呈上代码:

    #include <iostream>
    #include <algorithm>
    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    using namespace std;
    int dis[10010];
    int origin[10010],destination[10010],value[10010];//刚刚说过的三个数组
    int n,m;
    void Bellman_ford(int a)
    {
        memset(dis,88,sizeof(dis));//赋初始值
        dis[a]=0;
        for(int i=1;i<=n-1;i++)//更新n-1次
            for(int j=1;j<=m;j++)//更新每一条边
                dis[destination[j]]=min(dis[destination[j]],dis[origin[j]]+value[j]);//判断是否更新
     } 
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=m;i++)
            cin>>origin[i]>>destination[i]>>value[i];
        Bellman_ford(1);
        for(int i=1;i<=n;i++)
            cout<<dis[i]<<" "; 
    }

     有些人可能发现了,很多时候实际上不用更新n-1次,因此我们可以用队列优化:

    每次选出队首点,对与队首点链接的所有点的dis进行更新,并加入队列,然后队首点pop出队列,

    这个算法最好用邻接表实现,代码如下:

    #include <iostream>
    #include <algorithm>
    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    #include <queue>
    using namespace std;
    int dis[10010];
    int book[10010];
    int origin[10010],destination[10010],value[10010];
    int n,m;
    int total;
    int next[10010],head[10010];
    void adl(int a,int b,int c)//邻接表
    {
       total++;
       origin[total]=a;
       destination[total]=b;
       value[total]=c;
       next[total]=head[a];
       head[a]=total;
    }
    void Bellman_ford(int a)
    {
        memset(book,0,sizeof(book));//book[i]表示i号点是否在队列里
        memset(dis,88,sizeof(dis));
        queue <int> q;
        q.push(a);
        book[a]=1;
        dis[a]=0;
        while(!q.empty())//当队列不为空时更新
        {
            for(int e=head[q.front()];e;e=next[e])//枚举队首点相邻的每一个点
            {
                if(dis[destination[e]]>dis[origin[e]]+value[e])
                {
                    dis[destination[e]]=dis[origin[e]]+value[e];
                    if(book[destination[e]]==0)
                    {
                        q.push(destination[e]);//将更新的这一个点入队
                        book[destination[e]]=1;
                    }
                }
            }
            q.pop();//弹出队首元素
        }
     } 
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int a,b,c;
            cin>>a>>b>>c;
            adl(a,b,c);
       } 
        Bellman_ford(1);
        for(int i=1;i<=n;i++)
            cout<<dis[i]<<" "; 
    }

    总结一下,bellman_ford的空间复杂度是m时间复杂度是O(nm),经过队列优化,时间复杂度是<=O(nm)。

  • 相关阅读:
    JVM 综述
    看 Netty 在 Dubbo 中如何应用
    Netty 心跳服务之 IdleStateHandler 源码分析
    Netty 高性能之道
    Netty 解码器抽象父类 ByteToMessageDecoder 源码解析
    Netty 源码剖析之 unSafe.write 方法
    Netty 出站缓冲区 ChannelOutboundBuffer 源码解析(isWritable 属性的重要性)
    Netty 源码剖析之 unSafe.read 方法
    Netty 内存回收之 noCleaner 策略
    Netty 源码阅读的思考------耗时业务到底该如何处理
  • 原文地址:https://www.cnblogs.com/jason2003/p/7224580.html
Copyright © 2011-2022 走看看