Bellman-Ford(贝尔曼福特算法)

本文详细介绍了贝尔曼-福特算法的概念、特点,包括其在处理负权边图的优势,以及通过松弛操作和邮递员送信问题的应用实例。算法时间复杂度较高,适用于解决最短路径问题,特别在有负权重的情况下。
摘要由CSDN通过智能技术生成

简介

贝尔曼-福特算法(Bellman-Ford Algorithm)是一种用于求解单源最短路径问题的算法,它可以处理带有负权边的图。 该算法的实现思路是通过不断迭代松弛操作来更新最短路径,直到找到最优解。

名词解释:
1. 松弛操作:不断更新最短路径和前驱结点的操作。
2. 负权回路:绕一圈绕回来发现到自己的距离从0变成了负数,到各结点的距离无限制的降低,停不下来

算法特点

  • 贝尔曼-福特算法可以处理带有负权边的图,而迪杰斯特拉算法(Dijkstra's Algorithm)等其他最短路径算法无法处理这种情况。
  • 贝尔曼-福特算法的实现相对简单,易于理解和编程。
  • 贝尔曼-福特算法的时间复杂度为 O(nm),其中 n 是图中顶点的个数,m 是图中边的个数。 这使得它在稀疏图中效率较低。

如何理解贝尔曼-福特算法?

想象一下你正在一个城市中寻找最短路径。 你可以从一个路口开始,沿着道路前进,并记录下每个路口的距离。 贝尔曼-福特算法的工作方式类似:

  • 首先,将所有路口的距离设置为无穷大,并将起点距离设置为 0。

  • 然后,遍历所有道路,并对每个路口进行以下操作:

    • 如果当前路口的距离加上该道路的长度小于该道路终点的距离,则将该道路终点的距离更新为当前路口的距离加上该道路的长度。
  • 重复上述步骤 n - 1 次,其中 n 是路口的数量。

通过不断重复上述步骤,最终可以找到所有路口到起点的最短路径。

代码

void ford() {
    // 初始化距离数组,表示起点到每个顶点的距离为无穷大
    for (int i = 1; i <= n; i++) {
        dis[i] = INF;
    }
    dis[1] = 0; // 起点到自身的距离为0
    // 进行n-1次松弛操作
    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];
            }
        }
    }
}

如有不理解的可以上b站上看up主@简枫叶

总结

Bellman算法的核心就是松驰,没有贪心策略,也使它的时间复杂度比较高。因为它是单纯的松驰。首先我们要明白的是:如果处于第n层的节点,在它上一层的即n-1层所以节点的dist已经确定为最终真实值,那么通过一次遍历,第n层节点的dist也能被确定为最终真实值。第一次迭代,获得的信息是:与源点相邻点的真正dist(第二层节点),(其他点的可能仍为无穷大,或者为松驰一次状态);第二次循环,因为第二层的信息已经完全掌握,此次迭代是能确定第三层节点(从源点出发,经过2条边)的点的真实最短距离。(由于遍历的过程中,只完全掌握了第一层,其他节点的dist不能完全确定为最终的dist);如此循环,遍历n-1次,第n层的节点已经遍历完,至此,所有节点的dist都最终确定了(解释了为啥能求最短路)

例题P1629 邮递员送信

 邮递员送信

 题目描述

有一个邮递员要送东西,邮局在节点 1。他总共要送 n-1 样东西,其目的地分别是节点 2 到节点 n。由于这个城市的交通比较繁忙,因此所有的道路都是单行的,共有 m条道路。这个邮递员每次只能带一样东西,并且运送每件物品过后必须返回邮局。求送完这 n-1 样东西并且最终回到邮局最少需要的时间。

 输入格式

第一行包括两个整数,n 和 m,表示城市的节点数量和道路数量。

第二行到第 (m+1) 行,每行三个整数,u,v,w,表示从u 到 v 有一条通过时间为 w 的道路。

 输出格式

输出仅一行,包含一个整数,为最少需要的时间。

#include <stdio.h>

#define INF 99999999 // 定义无穷大值
#define MAXN 1005 // 最大顶点数
#define MAXM 100005 // 最大边数

int n, m; // 顶点数和边数
int u[MAXM], v[MAXM], w[MAXM]; // 边的起点、终点、权值
int dis[MAXN]; // 存储起点到各顶点的最短距离

// 初始化函数,读入顶点数、边数以及每条边的起点、终点、权值
void init() {
    scanf("%d %d", &n, &m); // 输入顶点数n和边数m
    for (int i = 1; i <= m; i++) {
        scanf("%d %d %d", &u[i], &v[i], &w[i]); // 输入每条边的起点、终点、权值
    }
}

// 反转边的起点和终点
void over() {
    for (int i = 1; i <= m; i++) {
        int temp = u[i];
        u[i] = v[i];
        v[i] = temp;
    }
}

// Ford算法求解最短路径
void ford() {
    // 初始化距离数组,表示起点到每个顶点的距离为无穷大
    for (int i = 1; i <= n; i++) {
        dis[i] = INF;
    }
    dis[1] = 0; // 起点到自身的距离为0
    // 进行n-1次松弛操作
    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];
            }
        }
    }
}

int main() {
    init(); // 初始化
    int ans = 0; // 记录总距离
    ford(); // 调用Ford算法计算从起点到各顶点的最短距离
    for (int i = 1; i <= n; i++) {
        ans += dis[i]; // 累加起点到各顶点的最短距离
    }
    over(); // 反转边的起点和终点
    ford(); // 重新计算最短路径
    for (int i = 1; i <= n; i++) {
        ans += dis[i]; // 累加起点到各顶点的最短距离
    }
    printf("%d\n", ans); // 输出总距离
    return 0;
}

  • 27
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值