【算法学习笔记】21:Bellman-Ford算法(有边数限制的单源点最短路)

本文详细介绍了Bellman-Ford算法的应用,包括计算有边数限制的单源点最短路以及检测负权回路。算法通过动态规划的思想,在限制步数的情况下求解最短路径,并阐述了如何利用算法检测负权回路。同时,对比了Bellman-Ford与SPFA算法的优劣,并给出了一道相关模板题的解决方案,强调了在存在负权边时的特殊处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 应用:计算有边数限制的单源点最短路

Bellman-Ford算法用于在存在负权边的图上,求单源点最短路,时间复杂度 O ( n m ) O(nm) O(nm)。但是因为该算法的改进版SPFA,在求单源点最短路的问题上几乎总是优于Bellman-Ford算法,所以Bellman-Ford算法一般只应用在对边的数量有限制的最短路问题,即限制经过边的数量不超过 k k k

Bellman-Ford算法属于动态规划算法。

Bellman-Ford算法的基本思想是,如果要求最短路的长度最多为 k k k(如果不限制,那其实就是 k = n − 1 k=n-1 k=n1,因为 > = n >=n >=n的路径一定有环,也就一定是负环了,但是存在负环的路径不能作为最短路,不然就可以一直转让路径越来越小),那么执行下面的过程:

dist[N]记录距离,last[N]作为dist[N]的副本,两者清空为正无穷
到源点1自己的距离是0,所以dist[1] = 0
循环k次
	将上轮算好的dist[N]记录到副本last[N]里,防止更新dist[N]时候用的不是上轮的值
	遍历所有m条边{a,b,w},表示从a到b有权值为w的边
	更新dist[b] = min(dist[b], last[a] + w),其思路也是看看先到a再到b会不会更短

最后,数组dist[N]里记录的就是从源点 1 1 1,不超过 k k k步,到其它所有点的最短距离。


如果求的是在最长 k k k步这个限制下的最短路,那么时间复杂度是 O ( k m ) O(km) O(km)

如果求的就是对路径长度 k k k没有要求的最短路,那么 k k k就是 n − 1 n-1 n1,这个时候算法的时间复杂度是 O ( m n ) O(mn) O(mn)


由于Bellman-Ford算法只要求能把所有边遍历就行了,所以在实现的时候既不需要用邻接矩阵存,也不需要写邻接表,直接开数组,把所有边存数组里就行了。

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

2 应用:检测负权回路

基于前面的思想,如果外层循环做到第 n n n轮的时候还有更新(指对dist的松弛操作在第 n n n轮也执行了),那么就一定存在一个长度为 n n n的最短路,由于一共就只有 n n n个点,所以这条路径上一定有环,也就一定是负环(才会越来越小,使其更新)。

所以Bellman-Ford算法把 k k k设置到 n n n就可以用来检测负权回路,只是一般检测负权回路不用Bellman-Ford算法来做,因为它时间复杂度比较高,而是用它的优化版——SPFA来做。

SPFA算法在很多方面都优于Bellman-Ford算法,所以Bellman-Ford算法主要还是应用在前面说的计算有边数限制的最短路这个问题上。不过如果直接用SPFA算法来求最短路,那么就要求图中不存在负环。

3 模板题:有边数限制的最短路

这题说了可能存在负权回路,但是因为这题是限制了边数不超过 k k k,所以这个时候路径是不能无限长的,也就不能在负环里面无限旋转,所以有负环也一样求最短路,这个“最短路”其实是“限制了边长度的最短路”。

注意,由于有负权边,所以取 m i n min min更新dist[N]这个数组的时候,正无穷的last[a]也是可能拿来更新dist[b]的,所以最后判断有没有到b的最短路的时候不能用== inf来判断,这里是用> inf / 2就认为是拿inf来更新过了,所以就不存在到这个点的最短路。

#include <iostream>
#include <cstring>

using namespace std;

const int N = 510, M = 1e4 + 10;

// 边数组,从a到b有权值为w的边
struct Edge {
    int a, b, w;
}edges[M];

// n个结点,m条边,最多k步最短路
int n, m, k;

// dist存到每个点不超过i步的最短路,经过k次松弛就是不超过k步的最短路
// last是dist的副本,防止一次外层循环里出现结点之间互相松弛的情况
int dist[N], last[N];

// 求最长不超过k步的最短路,存到dist数组里
void bellman_ford() {
    // 清空dist为inf,因为刚循环就会复制到last里,所以last不用清
    memset(dist, 0x3f, sizeof dist);
    // 源点到源点(编号1)的距离为0
    dist[1] = 0;
    // 最长不超过k步,所以松弛操作只执行k词
    for (int i = 0; i < k; i ++ ) {
        // 先将dist暂存到last里
        memcpy(last, dist, sizeof dist);
        // 对edges里存的每条边做松弛操作
        for (int j = 0; j < m; j ++ ) {
            auto& e = edges[j];
            dist[e.b] = min(dist[e.b], last[e.a] + e.w);
        }
    }
}

int main() {
    cin >> n >> m >> k;
    // 所有的边直接读入到数组里
    for (int i = 0; i < m; i ++ )
        cin >> edges[i].a >> edges[i].b >> edges[i].w;
    // 计算经过最多k步的最短路,存到dist里
    bellman_ford();
    // 注意由于存在负权边,inf可以拿来更新,所以判断不存在是用> inf/2
    if (dist[n] > 0x3f3f3f3f / 2) cout << "impossible" << endl;
    else cout << dist[n] << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值