一、前言
在之前的学习中,我们学习了用Dijkstra算法求有向图的最短路问题Dijkstra算法求最短路博客。但是在Djkstra算法中,我们认为可以走的步骤是无限的,但是在日常问题中,我们需要解决有步数限制的最短路问题,这个时候,我们就需要学习一个新的算法来解决这个问题----Bellman_Ford算法。虽然看上去又是一个高大上的算法,但其实这个算法的实现也并不是什么太难的事情。而且,它还能处理边的长度为负数的情况。
二、Bellman_Ford算法介绍
由于是有步数限制和负数边存在,我们不能使用Djkstra算法,但是Bellman_Ford算法的实现和Djkstra算法在思路上还是差不多的。
首先,初始化距离:起点到起点的距离为0,到其它点的距离都为无限大(红字代表到起点的距离,蓝字代表边的长度)
Bellman_Ford算法首先需要枚举步数,然后更新第i步后每个点距离起点的最短长度。具体实现方法是两层for循环,第一层循环步数,而第二层循环枚举每一条边a------>b长为w,比较当前的dis[b]与dis[a]+w,更新最小值。但是这里要注意,我们每次需要用一个数组te[N]来存下dis[N]用来下一步的更新,因为在算法公式中“比较当前的dis[b]与dis[a]+w,更新最小值”的操作我们需要用到dis,但是在每一次循环中,dis是不断更改的,因此我们需要用te数组存下上一次变化后的结果。
1.如图。第一次循环步数后,走完第一步后图变为
dis={0,1e9,1e9,1e9,1e9};
te={0,1e9,1e9,1e9,1e9};
1e9>te[1]+2=2,dis[2]=2;
dis={0,2,1e9,1e9,1e9};
2.继续循环到走二步,图变为
dis={0,2,1e9,1e9,1e9};
te={0,2,1e9,1e9,1e9};
1e9>te[2]-2=0;dis[3]=0
1e9>te[2]+1=3;dis[4]=3
dis={0,2,0,3,1e9};
3.走三步
dis={0,2,0,3,1e9};
te={0,2,0,3,1e9};
3>te[3]+2=2;dis[4]=2
1e9>te[4]+1=4;dis[5]=4
dis={0,2,0,2,4};
4.走四步
dis={0,2,0,2,4};
te={0,2,0,2,4};
4>te[4]+1=3;dis[5]=3
dis={0,2,0,2,5};
在上面的有向图中,最长能走4步,然后在上面的图中我们就枚举了步数为1~4步时的到各个点的最短的距离。
二、Bellman_Ford代码实现
例题链接:Acwing 有边数限制的最短路
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible。
注意:图中可能 存在负权回路 。
输入格式
第一行包含三个整数 n,m,k。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。
如果不存在满足条件的路径,则输出 impossible。
数据范围
1≤n,k≤500,
1≤m≤10000,
任意边长的绝对值不超过 10000。
输入样例:
3 3 1
1 2 1
2 3 1
1 3 3
输出样例:
3
这一道题就是Bellman_Ford算法的板子题,是需要看懂的,不过这里我们并不需要用到我们存图常用的链式前向星存图,因为我们只要遍历了所有的边就可以了,并没有要求要怎样遍历完所有的边。
看懂代码前你需要知道的
1.作者重写的mem函数“#define mem(a,b) memset(a,b,sizeof(a))”
2.memcpy函数
//#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 7;
int n, m, k;
int dis[N], te[N];
struct edge //边
{
int f, t, w; //定义边的起点,终点,长度
};
edge ed[N];
int Bellman_Ford()
{
mem(dis, 0x3f); //初始化距离为一个很大的数
dis[1] = 0; //起点到起点的距离为0
for (int i = 0; i < k; i++) //遍历步数
{
memcpy(te, dis, sizeof dis); //复制数组函数
for (int j = 1; j <= m; j++) //遍历每一条边
{
int f = ed[j].f, t = ed[j].t, w = ed[j].w;
dis[t] = min(dis[t], te[f] + w);
}
}
return dis[n]; //返回最后的答案
}
void solve()
{
cin >> n >> m >> k;
for (int i = 1; i <= m; i++) //输入每一条边
{
int a, b, c;
cin >> a >> b >> c;
ed[i] = { a,b,c };
}
int t=Bellman_Ford(); //用一个数来接收最后的答案
if (t > INF / 2) //如果这个数很大代表到不了,至于为什么不是">INF"在下方解释
cout << "impossible" << endl;
else //输出答案即可
cout <<t << endl;
}
int main()
{
//std::ios::sync_with_stdio(false);
//cin.tie(0), cout.tie(0);
solve();
return 0;
}
解释为什么不是">INF"而是“>INF/2”:由于题目中可能会有负数边存在,在公式"dis[t] = min(dis[t], te[f] + w);“中,如果dis[t]=1e9,w为-2,那么我们发现dis[t]被更新成了1e9-2,但是1e9-2实际上并不是代表路的长度为1e9-2,而是代表不能到达,根据题目中数据范围,我们可以发现,我们虽然不能写成”>INF",但是“>INF/2”是可以判断是否能到达终点的。
作者:Avalon Demerzel,喜欢我的博客就点个赞吧,更多图论与数据结构知识点请见作者专栏《图论与数据结构》