算法学习笔记(26)——Bellman-Ford算法(单源最短路)

Bellman-Ford 算法

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

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

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

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

\(n\) 次循环后一定满足dist[b] <= dist[a] + w,迭代 \(k\) 次,则求出不超过 \(k\) 条边的最短距离。

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

  • 如果求的是在最长 \(k\) 步这个限制下的最短路,那么时间复杂度是 \(O(km)\)
  • 如果求的就是对路径长度 \(k\) 没有要求的最短路,那么 \(k\) 就是 \(n-1\),这个时候算法的时间复杂度是 \(O(mn)\)

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

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

题目链接:AcWing 853. 有边数限制的最短路

#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;
}
posted @ 2022-12-10 09:32  S!no  阅读(130)  评论(0编辑  收藏  举报