简介
贝尔曼-福特算法(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;
}