[Acwing算法基础] 3.6 Bellman-Ford

这篇博客探讨了Dijkstra算法无法处理负权边的原因,并通过一个实例展示了其局限性。然后,重点介绍了Bellman-Ford算法,这是一种适用于包含负权边但不含负权环的图的单源最短路径算法。博客解释了算法的工作原理,包括使用backup数组防止负权边导致的错误更新,并讨论了为什么检查dist[n]是否大于0x3f3f3f3f/2来判断最短路是否存在。最后,提供了完整的C++实现代码作为示例。
摘要由CSDN通过智能技术生成

Bellman-Ford算法使用于含有负权边但不含负环的情况

dijkstra不能解决负权边是因为 dijkstra要求每个点被确定后st[j] = true,dist[j]就是最短距离了,之后就不能再被更新了(一锤子买卖),而如果有负权边的话,那已经确定的点的dist[j]不一定是最短了

考虑下面地一个例子:
在这里插入图片描述
dijkstra详细步骤

  • 初始dist[1] = 0
  • 找到了未标识且离源点1最近的结点1,标记1号点,用1号点更新其他所有点的距离,2号点被更新成dist[2] = 23号点被更新成dist[3] = 5
  • 找到了未标识且离源点1最近的结点2,标识2号点,用2号点更新其他所有点的距离,4号点被更新成dist[4] = 4
  • 找到了未标识且离源点1最近的结点4,标识4号点,用4号点更新其他所有点的距离,5号点被更新成dist[5] = 5
  • 找到了未标识且离源点1最近的结点3,标识3号点,用3号点更新其他所有点的距离,4号点被更新成dist[4] = 3
  • 结束
  • 得到1号点到5号点的最短距离是5,对应的路径是1 -> 2 -> 4 -> 5,并不是真正的最短距离

bellman-ford 算法思想

Bellman - ford 算法是求含负权图的单源最短路径的一种算法,效率较低。遍历k次(这个k是路径上经过的边的数量);每一次遍历,会遍历所有边,同时更新所有边的距离。

为什么要使用backup数组

更新距离的代码如下:

for (int i = 0; i < k; i ++ )
{
    memcpy(backup, dist, sizeof dist);
    for (int j = 0; j < m ; j ++ )
    {
        int a = edges[j].a, b = edges[j].b, w = edges[j].w;
        dist[b] = min(dist[b], backup[a] + w);  // 这里要使用backup数组防止串联
    }
}

串联的例子如下:(即:当k = 1时,1到3的距离应该是无穷大,但如果不使用backup数组,那么就会发生串联(3的距离就会变成2))
在这里插入图片描述

为什么是dist[n]>0x3f3f3f3f/2, 而不是dist[n]>0x3f3f3f3f

因为最后一个可能是无穷大减去若干个负数
在这里插入图片描述

完整代码

#include <cstring>
#include <algorithm>
#include <algorithm>

using namespace std;

const int N = 510, M = 10010;
int n, m, k;
int dist[N], backup[N];

struct Edge
{
    int a, b ,w;
}edegs[M];

int bellman_ford()
{
    // 初始化
    memset(dist, 0x3f3f3f3f, sizeof dist);
    dist[1] = 0;
    
    for (int i = 0; i < k; ++i)
    {
        // 只用上一次迭代的结果,那么就不会出现串联了
        // 1. backup数组防止串联
        memcpy(backup, dist, sizeof dist);  
        for (int j = 0; j < m; ++j)
        {
            int a = edegs[j].a, b = edegs[j].b, w = edegs[j].w;
            dist[b] = min(dist[b], backup[a] + w);
        }
    }
    
    // 这里不是无穷大, 而是比无穷大要小
    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    
    for (int i = 0; i < m; ++i)
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edegs[i] = {a, b, w};
    }
    
    int t = bellman_ford();
    
    if (t == -1)  // 说明最短路长度不存在
        puts("impossible");
    else printf("%d\n", t);
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东阳z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值