acwing ----bellman-ford与spfa算法
1.bellmax_ford算法:
其特点:能够用来求有边数限制的最短路,以及带负环的最短路,有以上这些特性都是因为:
for (int i=0;i<edgenum;i++)其最外边的for 循环,其含义是决定了从起点到终点一共用了多少条边。
1 给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。 2 3 请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible。 4 5 注意:图中可能 存在负权回路 。 6 7 输入格式 8 第一行包含三个整数 n,m,k。 9 10 接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。 11 12 输出格式 13 输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。 14 15 如果不存在满足条件的路径,则输出 impossible。 16 17 数据范围 18 1≤n,k≤500, 19 1≤m≤10000, 20 任意边长的绝对值不超过 10000。 21 22 输入样例: 23 3 3 1 24 1 2 1 25 2 3 1 26 1 3 3 27 输出样例: 28 3
这个算法的核心思想是每一个点都在一次循环中更新,因为只要保证能够遍历到每一条边,所以其用的数据结构有很多,这里直接用struct 即可:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, M = 10010;
struct Edge
{
int a, b, c;
}edges[M];
int n, m, k;
int dist[N];
int last[N];//这里的last是保存上一个状态的dist,因为我们不能够用一个正在更新的dist去更新:
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 ++ )
{
auto e = edges[j];
dist[e.b] = min(dist[e.b], last[e.a] + e.c);
}
}
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edges[i] = {a, b, c};
}
bellman_ford();
if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");
else printf("%d\n", dist[n]);
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48523/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
/*3) ⭐️Bellman_ford算法里最后return-1的判断条件写的是dist[n]>0x3f3f3f3f/2;
而spfa算法写的是dist[n]==0x3f3f3f3f;其原因在于Bellman_ford算法会遍历所有的边,因此不管是不是和源点连通的边它都会得到更新;
但是SPFA算法不一样,它相当于采用了BFS,因此遍历到的结点都是与源点连通的,因此如果你要求的n和源点不连通,它不会得到更新,还是保持的0x3f3f3f3f。*/
对上面的last数组的解释:
如这张图:如果我们要求只用一条边从1到3,如果我们没有last数组则:
更新时:dist[2]=min(dist[2],dist[1]+1)=1;
dist[3]=min(dist[3],dist[2]+1)=2;//到这里其实已经隐含了1-》3是由:1->2->3不行,如果有last数组:这里的dist[2]=0x3f3f3f3f则dist[3]=0x3f3f3f3f不会变
dist[3]=min(dist[3],dist[1]+1)=2;
2.spfa算法:
其实其是对bellman_ford算法的优化: ⭐️ Bellman_ford算法可以存在负权回路,是因为其循环的次数是有限制的因此最终不会发生死循环;但是SPFA算法不可以,由于用了队列来存储,只要发生了更新就会不断的入队,因此假如有负权回路请你不要用SPFA否则会死循环。
/--------------------------------------------------------------------------------/
在bellman_ford 算法中for(int j=0;j<m;j++)dist[...]=......
这一步操作有很多无意义的,其实只有当dist[a]更新过,dist[b]才会变(其实就是有边相连的两个点):我们可以用bfs找一下dist会变的点,
用queue装一下,然后用这里面的点更新:
1 给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。 2 3 请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。 4 5 数据保证不存在负权回路。 6 7 输入格式 8 第一行包含整数 n 和 m。 9 10 接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。 11 12 输出格式 13 输出一个整数,表示 1 号点到 n 号点的最短距离。 14 15 如果路径不存在,则输出 impossible。 16 17 数据范围 18 1≤n,m≤105, 19 图中涉及边长绝对值均不超过 10000。 20 21 输入样例: 22 3 3 23 1 2 5 24 2 3 -3 25 1 3 4 26 输出样例: 27 2
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 100010;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while (q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = spfa();
if (t == 0x3f3f3f3f) puts("impossible");
else printf("%d\n", t);
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48498/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
spaf算法也能判断负环:在这里:https://www.acwing.com/solution/content/42308/