bellman-ford算法

bellman-ford算法


前言

前面介绍了Dijkstra算法主要用来求不含负权边的单源最短路,但是Dijkstra并不能解决带有负权边的问题,而bellman-ford算法能完美解决负权边的问题。


一、Bellman-Ford算法的思路

主要思路

bellman-ford算法的主要思路:首先进行两重循环,第一重循环循环n-1(顶点个数)遍,内层循环循环每一条边,进行松弛操作(dist[b] =min(dist[b],dist[a] + w)),更新一下最短距离。

核心代码

for (int i = 0; i < n-1; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            dist[b] = min(dist[b], dist[a] + w);
        }
    }
}

第一重循环的意义

第一重循环的意义是表示从起点经过不超过n-1条边走到每个点的最短距离,因此bellman-ford算法可以求在有边数限制下到达n号点的最短距离。
同时如果我们在只有n个点的图中循环第n次仍然更新,那么说明在这个图中存在一条边数为n的最短路径,则可以得出该图中存在负环的结论。(因此bellman-ford算法可以查找该图中是否存在负环)

第二重循环的意义

第二重循环就是循环每一条边,进行松弛操作,更新最短路径,
松弛操作就是对比从A点直接到c点,或者从A到B再到C这两条路径谁更短来更新最短路径 在这里插入图片描述

二、例题解析

题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。 请你求出从1号点到 n 号点的最多经过 k
条边的最短距离,如果无法从1号点走到 n 号点,输出 impossible。

注意:图中可能存在负权回路。

输入格式

第一行包含三个整数 n,m,k。

接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

点的编号为 1∼n。

输出格式

输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。

如果不存在满足条件的路径,则输出 impossible。

题目思路

首先通过结构体将图存入,跟bellman-ford算法思路一致通过两重循环,找最短路径,但是这个题有一个点需要特殊处理,
就是在进入新的一次外循环之前我们需要将之前结果备份一下,来保证我们更新的时候只用上一次迭代的结果,这样就能确保每次进行松弛操作时不会将上次内循环的结果混用,造成串联的结果。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100000;
int a[N], b[N], c[N];
int dist[N], last[N];
int n, m, k;

void bellman_ford() {
	memset(dist, 0x3f, sizeof dist);

	dist[1] = 0;
	for (int i = 0; i < k; i ++ ) {
		memcpy(last, dist, sizeof dist);
		for (int j = 0; j < m; j ++ ) {
			dist[b[j]] = min(dist[b[j]], last[a[j]] + c[j]);
		}
	}
}

int main() {
	cin >> n >> m >> k;

	for (int i = 0; i < m; i++)
		cin >> a[i] >> b[i] >> c[i];

	bellman_ford();

	if (dist[n] > 0x3f3f3f3f / 2)
		cout<<"impossible";
	else
		cout << dist[n] << endl;
	return 0;
}

题目描述

当走访自己的农场时,农夫 John(下称 FJ)发现了许多神奇的虫洞。虫洞是一个很奇怪的东西,因为它是一个能够将你送到一个时间位于你进入虫洞之前的目的地的单向路径。FJ的每个农场包含 N 块田(1 ≤ N ≤ 500),编号从1到 N ,M 条路(1 ≤ M ≤ 2500),W 个虫洞(1 ≤ W ≤200)
FJ是一个狂热的时间旅行爱好者,他希望做这样的事:从某块田开始,经过一些路径和虫洞,最后回到起始位置且到达时的时间在他出发之前。他有可能与他自己碰面,为了帮助 FJ 求出这是否可能成功,他会告诉你他的农场的 F 个地图(1 ≤ F ≤ 5)。没有路径花费超过10000秒才能走完,也没有虫洞能够将 FJ 带回早于进入虫洞的时刻的10000秒之前。

输入格式

第一行:一个整数 F ,描述如上。 接下来对每个农场,第一行三个整数,空格分隔:N、M、W。 接下来 M
行,每行三个整数,空格分隔:S、E、T,分别描述:存在点 S 到点 E 的双向路径,单程花费 T 秒。两块田之前可以被超过一条路连接。
接下来 W 行,每行三个整数,空格分隔:S、E、T,分别描述:存在点 S 到点 E 的单向路径,能够令进入者回到 T 秒之前。

输出格式

1到 F 行:对每个农场,如果 FJ 可以达成他的目标,
输出
YES
否则输出
NO

对农场1,FJ 并不能穿越到出发之前。 对农场2,FJ 可以沿闭环1→2→3→1行进,在他出发之前回到出发地
1。他可以从这个环路上的任意位置出发来完成目标。

Sample Input

2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8

Sample Output

NO
YES

题目思路

此题就是判断图里是否有一个负权边,使得自己能够回到出发前和自己碰面,只要循环nci就可以判断是否存在负权边,再判断该负权边是否符合题意就行了

代码

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

using namespace std;

const int N = 100000;

int n, m, w, k;
int dist[N];
int last[N];
int na[N], nb[N], nw[N];


bool Bellman_Ford() {

	memset(dist, 0x3f, sizeof(dist));

	dist[1] = 0;

	for (int i = 1; i < n; i ++) {
		memcpy(last, dist, sizeof(last));
		for (int j = 1; j <= k; j ++) {
			int a = na[j], b = nb[j], w = nw[j];

			dist[b] = min(dist[b], dist[a] + w);
		}

		bool flag = false;
		for (int j = 1; j <= n; j ++) {
			if (dist[j] != last[j]) {
				flag = true;
				break;
			}
		}

		if (!flag)
			break;
	}

	for (int j = 1; j <= k; j ++) {
		int a = na[j], b = nb[j], w = nw[j];

		if (dist[b] > dist[a] + w) {
			return false;
		}
	}

	return true;
}

int main() {
	int t;
	cin >> t;

	while (t -- ) {
		k = 0;

		cin >> n >> m >> w;

		for (int i = 1; i <= m; i ++) {
			k++;
			cin >> na[k] >> nb[k] >> nw[k];
			k++;
			nb[k] = na[k - 1], na[k] = nb[k - 1], nw[k] = nw[k - 1];
		}

		for (int i = 1; i <= w; i ++) {
			k++;
			cin >> na[k] >> nb[k] >> nw[k];
			nw[k] *= -1;
		}

		bool t = Bellman_Ford();

		if (!t)
			puts("YES");
		else
			puts("NO");
	}

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值