Bellman-Ford解决单源最短路径(负权边)

1.案例描述

求下图1号节点到所有节点之间的最短距离。
在这里插入图片描述
给出的边的信息如下(节点u—>节点v,边的权重是w):
u v w
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3

2.数据结构

将所有的边的信息存储于数组中,其中数组u[i]表示开始节点,数组v[i]表示结束节点,w[i]表示从节点u[i]到节点v[i]的权重。数组dis存储1号顶点(源点)到所有顶点的距离。四个数组的初始化状态均如下:
在这里插入图片描述
因为初始化数组dis的时候默认为顶点1还没有边相连,所以设置顶点1到其余各个顶点的距离均为∞。

3.算法思想与具体步骤

算法的基本思想为:对所有的边进行n-1次“松弛”操作。所谓“松弛”操作就是每次加入新的节点i,将节点i作为中间节点,判断源点—>节点i—>各个节点的最短距离 较之于 上一次的最短距离是否会有更新。
在案例中,具体过程可以理解为如下几个步骤:

  1. 数据结构的初始化(见第2章),如下图:
    在这里插入图片描述
    可以认为当前的图像只有一个节点,如下所示:
    在这里插入图片描述

  2. 第①轮对所有边进行松弛。

边信息松弛过程是否更新
节点2—>节点3,w= 2dis[3] = ∞;(dis[2] = ∞) +( w = 2),不能使dis[3]变小(初始化1->3和路径1->2->3比较)
节点1—>节点2,w= -3dis[2] = ∞;(dis[1] = 0) +( w = -3) = -3,能使dis[2]变小(初始化1->2和路径1->2比较)更新dis[2] = -3
节点1—>节点5,w= 5dis[5] = ∞ ; (dis[1] = 0) +( w = 5) = 5,能使dis[5]变小(初始化1->5和路径1->5比较)更新dis[5] = 5
节点4—>节点5,w= 2dis[5] = ∞;(dis[4] = ∞ )+ (w = 2),不能使dis[5]变小(初始化1->5和路径1->4->3比较)
节点3—>节点4,w= 3dis[4] = ∞;(dis[3] = ∞ )+ (w = 3),不能使dis[4]变小(初始化1->5和路径1->3->4比较)

按照如上所述,数据结构更新为:
在这里插入图片描述
3. 第②轮对所有边进行松弛。

边信息松弛过程是否更新
节点2—>节点3,w= 2dis[3] = ∞;(dis[2] = -3) +( w = 2) = -1,可以使dis[3]变小(初始化1->3和路径1->2->3比较)更新dis[3] = -1
节点1—>节点2,w= -3dis[2] = -3;(dis[1] = 0) +( w = -3) = -3,不能使dis[2]变小(同一条路径原地比较)
节点1—>节点5,w= 5dis[5] = 5 ; (dis[1] = 0) +( w = 5) = 5,不能使dis[5]变小(同一条路径原地比较)
节点4—>节点5,w= 2dis[5] = 5;(dis[4] = ∞ )+ (w = 2),不能使dis[5]变小(1->5和路径1->4->5比较)
节点3—>节点4,w= 3dis[4] = ∞;(dis[3] = -1 )+ (w = 3) = 2,能使dis[4]变小(初始化1->4和路径1->3->4比较),dis[3] = -1 )为这一步更新更新dis[4] = 2

按照如上所述,数据结构更新为:
在这里插入图片描述
4. 第③轮对所有边进行松弛。

边信息松弛过程是否更新
节点2—>节点3,w= 2dis[3] = -1;(dis[2] = -3) +( w = 2) = -1,不能使dis[3]变小(同一条路径原地比较)
节点1—>节点2,w= -3dis[2] = -3;(dis[1] = 0) +( w = -3) = -3,不能使dis[2]变小(同一条路径原地比较)
节点1—>节点5,w= 5dis[5] = 5 ; (dis[1] = 0) +( w = 5) = 5,不能使dis[5]变小(同一条路径原地比较)
节点4—>节点5,w= 2dis[5] = 5;(dis[4] = 2 )+ (w = 2) = 4,能使dis[5]变小(1->5和路径路径1->2->3->4->5比较)更新 dis[5] = 4
节点3—>节点4,w= 3dis[4] = 2;(dis[3] = -1 )+ (w = 3) = 2,不能使dis[4]变小(同一条路径原地比较)

按照如上所述,数据结构更新为:
在这里插入图片描述
5. 第④轮对所有边进行松弛。

边信息松弛过程是否更新
节点2—>节点3,w= 2dis[3] = -1;(dis[2] = -3) +( w = 2) = -1,不能使dis[3]变小(同一条路径原地比较)
节点1—>节点2,w= -3dis[2] = -3;(dis[1] = 0) +( w = -3) = -3,不能使dis[2]变小(同一条路径原地比较)
节点1—>节点5,w= 5dis[5] = 5 ; (dis[1] = 0) +( w = 5) = 5,不能使dis[5]变小(同一条路径原地比较)
节点4—>节点5,w= 2dis[5] = 5;(dis[4] = 2 )+ (w = 2) = 4,不能使dis[5]变小(同一条路径原地比较)
节点3—>节点4,w= 3dis[4] = 2;(dis[3] = -1 )+ (w = 3) = 2,不能使dis[4]变小(同一条路径原地比较)

至此,所有更新均已完成。

总结

第①轮在对所有的边进行松弛之后,得到的是从1号节点“只能经过一条边”到达其余各顶点的最短路径长度。
第②轮在对所有的边进行松弛之后,得到的是从1号节点“最多经过两条边”到达其余各顶点的最短路径长度。
………………
第k轮在对所有的边进行松弛之后,得到的是从1号节点“最多经过k条边”到达其余各顶点的最短路径长度。
总共需要经过vertice - 1轮就可以得到源点到其他所有节点之间的最短距离,因为任意两点之间的最短路径最多包含vertice - 1个边。

4.回路的判断,最短路径与回路的判断

该算法也可用于判断图中是否有回路。
回路分为正权回路(回路权值之和为正)和负权回路(回路权值之和为负)。
case1:如果最短路径中包含正权回路,那么去掉这个这个回路,一定可以得到更短的路径。
case2:如果最短路径中包含负权回路,那么肯定没有最短路径,因为每多走一次负权回路就可以得到更短的路径。
因此,最短路径肯定不包含回路。
那么如何判断是否有负权回路,见第5部分的代码。

5.具体实现

#include <vector>
#include <iostream>

using namespace std;

class Bellman_Ford
{
private:
    int vertice = 0;//顶点数
    int edge = 0;//边数
    
    vector<int> u;
    vector<int> v;
    vector<int> w;
    vector<int> dis;//源点到各个顶点之间的最短距离

public:
    //根据节点值和边值初始化:边的起始节点数组u,边的终止节点数组v,边u[i]->v[i]的权重w
    Bellman_Ford(int x, int y) :vertice(x), edge(y)
    {
        //图的初始化从下标1开始
        dis.resize(vertice + 1);
        u.resize(edge + 1);
        v.resize(edge + 1);
        w.resize(edge + 1);
    }
    //检测负权回路
    bool Detect_negative_weight_circuit()
    {
        bool flag = false;
        for (int i = 1; i <= edge; i++)
        {
            if (dis[v[i]] > dis[u[i]] + w[i])
            {
                flag = 1;
            }           
        }
        return flag;
    }
    //读入图的边,并且根据边的信息初始化数组dis,数组book
    void GetEdgeInfo()
    {
        cout << "输入边的信息(节点1,节点2,权重):" << endl;
        int e1 = 0, e2 = 0, weigth = 0;
        for (int i = 1; i <= edge; i++)
        {
            cin >> e1 >> e2 >> weigth;
            u[i] = e1;
            v[i] = e2;
            w[i] = weigth;
        }
        for (int i = 2; i <= vertice; i++)
        {
            //dis[1]在构造函数里面已经初始化为0
            dis[i] = INT_MAX;
        }
    }

    //打印
    void Print()
    {
        for (int i = 1; i <= vertice; i++)
        {

            cout << dis[i] << "    ";
        }
        cout << endl;
    }

    //Bellman_Ford核心思想
    void Bellman_Ford_Alg()
    {
        for (int k = 1; k < vertice; k++)//控制松弛的轮数
        {
            bool check = false;//标记在本轮松弛中数组dis是否会发送更新
            //找离1号节点最近的节点(找数组dis中的最小值)           
            for (int i = 1; i <= edge; i++)
            {
                if (dis[u[i]] < INT_MAX && dis[v[i]] > (dis[u[i]] + w[i]))
                {
                    dis[v[i]] = (dis[u[i]] + w[i]);
                    check = true;//如果数组dis发生变化,check的值就改变
                }
            }
            //松弛结束后判断dis数组是否发生变化
            if (check == false)
            {
                break;
            }
            
        }
    }

};

int main()
{
    Bellman_Ford Bellman(5, 5);
    Bellman.GetEdgeInfo();

    cout << "初始信息:" << endl;
    Bellman.Print();

    Bellman.Bellman_Ford_Alg();


    cout << "单源最短路径(顶点1到其余各顶点):" << endl;
    Bellman.Print();

    bool tag = Bellman.Detect_negative_weight_circuit();
    if (tag)
    {
        cout << "存在负权回路" << endl;
    }
    else
    {
        cout << "不存在负权回路" << endl;
    }
    

    return 0;
}

在这里插入图片描述

6.总结

时间复杂度:O(vertice * edge)

7.END!!!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值