因为近期在学图,所以顺带的写一篇最短路的备战蓝桥杯文章。
最短路(单源)
所有边权都为正数有两种算法:
1.朴素Dijkstra O(n^2)
2.堆优化的Dijkstra O(mlogn)
存在负权边有两种算法:
1.Bellman-Ford O(nm)
2.SPFA 一般O(m), 最坏 O(nm)
今天,我来介绍一下Bellman-Ford(存在负权+有边数限制)
存在负权且有边数限制——》Bellman-Ford(在我所学算法中 只能用个算法)
首先
我们先复习一下Bellman-Ford 算法
简单来讲Bellman-Ford 算法核心代码只有4行(双重for循环)
for(k = 1; k <= n - 1; k++)
for(i = 1; i <= m; i++)
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
特别的(要注意的):
relax(松弛)操作
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
最后附上 Bellman-Ford 的板子:
// 算法核心语句
for (k = 1; k <= n - 1; k++) {
for (i = 1; i <= m; i++) {
if (dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
//或直接写成
//dis[v[i]] = min(dis[v[i]],dis[u[i]] + w[i]);
}
}
//下面代码是用来检测负权回路的
// 若题需要检查负权回路则加上,若不需要则无需写!!!
int flag = 0;
for(i = 1; i <= m; i++)
if(dis[v[i]] > dis[u[i]] + w[i])
flag = 1;
if(flag == 1)
printf("此图是负权回路");
话不多说,还是以例题讲解(滑稽保命)
题目如下:
给定一个 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
AC代码如下:
#include
#include
#include
#include
using namespace std;
const int N = 1e4+9;
struct nn
{
int x,y,z;
}s[N];
int dist[N],backup[N];
int n,m,k;
int bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1] = 0;
for(int i = 1;i<=k;i++)
{
memcpy(backup,dist,sizeof dist);
for(int j = 1;j<=m;j++)
{
int x = s[j].x, y = s[j].y, z = s[j].z;
dist[y] = min(dist[y],backup[x]+z);
}
}
return dist[n];
}
int main()
{
scanf("%d %d %d",&n,&m,&k);
for(int i = 1;i<=m;i++)
{
scanf("%d %d %d",&s[i].x,&s[i].y,&s[i].z);
}
int ans = bellman_ford();
if(ans>0x3f3f3f3f/2)
puts("impossible");
else
printf("%d\n",ans);
}
代码解析(题解):
这个题就是典型的 有边数限制的最短路(且有负权边)问题
那我们就直接套Bellman-Ford 的板子就可以了
不过我们需要注意一点——》避免 串联
如何去实现呢?
首先我们要开一个备份数组backup
最后在Bellman-Ford的基础上进行修改:
for(int i = 1;i<=k;i++)
{
memcpy(backup,dist,sizeof dist);
for(int j = 1;j<=m;j++)
{
int x = s[j].x, y = s[j].y, z = s[j].z;
dist[y] = min(dist[y],backup[x]+z);
}
}
每次relax(松弛)都用上一次的结果进行(这样就很好的避免串联了)
如何做到有边数限制的最短路呢?
不难发现,用外层for控制
注:memcpy的效率很高
最后,感谢您的阅读!!!
每一发奋努力的背后,必有加倍的赏赐。