zoukankan      html  css  js  c++  java
  • Bellman-Ford算法

    算法适用

    当在图中出现负权的时候,使用Dijkstra算法将不能在正确的求出结果,所以引入Bellman-Ford算法

    算法原理

    该算法的原理其实就如同Dijkstra算法一样进行边的松弛

    松弛一轮后的结果就是初始节点只经过一条边到达其余各个节点的最短路径长度,因为只检测了添加一个点对距离的影响

     那么松弛的轮数最多是n-1,因为n个顶点的图最多包含n-1条边,而且有可能在n-1轮松弛之前就已经完成了dis数组的更新不再变化

    例如:

     

     (3 4之间dis未发生变化)

     算法核心代码

    for (int k = 1; k <=n - 1; k++)
            for (int i = 1; i <= m; i++)
                if (dis[v[i]]>dis[u[i]] + w[i])
                    dis[v[i]] = dis[u[i]] + w[i];//进行n-1次的松弛

    算法完整代码

    #include<iostream>
    #include<algorithm>
    using namespace std;
    int dis[200], n, m, u[200], v[200], w[200];
    #define inf 0x3f3f3f3f 
    int min = inf;
    int start = 1;//设置起始节点为1号节点
    int main()
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <=m; i++)
            scanf("%d%d%d", &u[i], &v[i], &w[i]);
        fill(dis, dis + 200, inf);//初始化距离数组
        dis[start] = 0;//起始节点的距离值设置为0
    
        for (int k = 1; k <=n - 1; k++)
            for (int i = 1; i <= m; i++)
                if (dis[v[i]]>dis[u[i]] + w[i])
                    dis[v[i]] = dis[u[i]] + w[i];//进行n-1次的松弛
    
        //检测负权回路
        bool flag = false;
        for (int i = 1; i <= m; i++)
            if (dis[v[i]] > dis[u[i]] + w[i])//在进行了n-1次后的松弛后如果还有dis[v[i]] > dis[u[i]] + w[i]就是存在回路
                flag = true;
    
        for (int i = 1; i <= n; i++)
            cout << dis[i] << ' ';
    
    }
    //5 5
    //2 3 2
    //1 2 -3
    //1 5 5
    //4 5 2
    //3 4 3

    检测负权回路

    算法简单优化

    由于算法可能在n-1次松弛之前就完成任务,所以可以设置一个back数组记录dis,如果在迭代之后dis没有变变化就结束松弛

    检测负权回路+算法简单优化代码

    #include<iostream>
    #include<algorithm>
    using namespace std;
    int dis[200],back[200],n, m, u[200], v[200], w[200];
    #define inf 0x3f3f3f3f 
    int min = inf;
    int main()
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++)
            scanf("%d%d%d", &u[i], &v[i], &w[i]);
        fill(dis, dis + 200, inf);
        dis[1] = 0;
        int check = 0;
        for (int k = 1; k <= n - 1; k++)
        {
            for (int i = 1; i <= n; i++)back[i] = dis[i];//使用back数组记录dis数组
            for (int i = 1; i <= m; i++)
                if (dis[v[i]] > dis[u[i]] + w[i])
                    dis[v[i]] = dis[u[i]] + w[i];
            for (int i = 1; i <= n; i++)
            {
                if (back[i] != dis[i]) { check = 1; break; }//如果数组未发生变化就直接中断
            }
            if (check == 0)break;
        }
    
        //检测负权回路
        bool flag = false;
        for (int i = 1; i <= m; i++)
            if (dis[v[i]] > dis[u[i]] + w[i])
                flag = true;
    
        for (int i = 1; i <= n; i++)
            cout << dis[i] << ' ';
    
    }

    邻接表的数组表示方法

     

     

     简单总结一下就是first[u[i]]是保存顶点u[i]的第一条边的编号,而next保存编号为i的边下一条边编号

    算法的队列优化

    能满足if (dis[v[k]] > dis[u[k]] + w[k])的k值并且在队列中不存在的值,才会进入队列,这样从队列中取数松弛,才保证每次都进行有效的更新

     代码

    #include<iostream>
    #include<algorithm>
    #include<queue>
    using namespace std;
    int dis[200],n, m, u[200], v[200], w[200],first[200],nexts[200],visited[200];
    //first[200],nexts[200]分别是邻接表中的数组,visited是用来记录队列中是否存在元素,否则需要一次次遍历寻找
    queue<int>que;
    int start = 1;
    #define inf 0x3f3f3f3f 
    int min = inf;
    int main()
    {
        scanf("%d%d", &n, &m);
        
        fill(dis, dis + 200, inf);//初始化距离数组
        dis[start] = 0;//起始节点的距离值设置为0
        
        fill(first, first + n, -1);//first数组以及next数组初始化为-1
        fill(nexts, nexts + n, -1);
        for (int i = 1; i <= m; i++)
        {
            scanf("%d%d%d", &u[i], &v[i], &w[i]);
            //建立邻接表
            nexts[i] = first[u[i]];
            first[u[i]] = i;
        }
        que.push(start);//起点入队
        visited[start] = 1;
        while (!que.empty())
        {
            int k = first[que.front()];//第一条边的编号
            while (k != -1)
            {
                if (dis[v[k]] > dis[u[k]] + w[k])
                {
                    dis[v[k]] = dis[u[k]] + w[k];
                    if (visited[v[k]] == 0)//如果队中不存在就入队
                    {
                        que.push(v[k]);
                        visited[v[k]] = 1;
                    }
                }
                k = nexts[k];//k变为下一条边的编号
            }
            visited[que.front()] = 0;//出队
            que.pop();
        }
    
        for (int i = 1; i <= n; i++)
            cout << dis[i] << ' ';
    
    }
  • 相关阅读:
    Android中没有插入SD情况下的文件写入和读取
    Android在OnCreate中获取控件的宽度和高度
    Android Adapter遇到的崩溃问题
    IIS Server Application Unavailable 解决方法
    Silverlight跨域访问WebService解决方法
    Silverlight 与 JS交互
    Dell 1440 黑苹果 (从10.6.3升级到10.6.8)
    Android控件系列之相册Gallery&Adapter适配器入门&控件缩放动画入门
    Android颜色编辑器的制作中遇到的问题
    马云讲给在工厂里工作的员工的话(转自qq)
  • 原文地址:https://www.cnblogs.com/Jason66661010/p/12872639.html
Copyright © 2011-2022 走看看