bellman-ford算法

bellman-ford算法

文章目录

    • bellman-ford算法
  • 前言
  • 一、Bellman-Ford算法的思路
    • 主要思路
    • 核心代码
    • 第一重循环的意义
    • 第二重循环的意义
  • 二、例题解析
    • 题目描述
    • 输入格式
    • 输出格式
    • 题目思路
    • 题目描述
    • 输入格式
    • 输出格式
    • Sample Input
    • Sample Output
    • 题目思路
    • 代码


前言

前面介绍了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这两条路径谁更短来更新最短路径 bellman-ford算法_第1张图片

二、例题解析

题目描述

给定一个 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 
#include 
#include 
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 
#include 
#include 

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;
}

你可能感兴趣的:(ACM每周知识点,算法,图论,数据结构)