Bellman-Ford算法使用于含有负权边但不含负环的情况
dijkstra不能解决负权边是因为 dijkstra要求每个点被确定后st[j] = true
,dist[j]就是最短距离了,之后就不能再被更新了(一锤子买卖),而如果有负权边的话,那已经确定的点的dist[j]不一定是最短了
考虑下面地一个例子:
dijkstra详细步骤
- 初始
dist[1] = 0
- 找到了未标识且离源点1最近的结点
1
,标记1
号点,用1
号点更新其他所有点的距离,2
号点被更新成dist[2] = 2
,3
号点被更新成dist[3] = 5
- 找到了未标识且离源点
1
最近的结点2
,标识2
号点,用2
号点更新其他所有点的距离,4
号点被更新成dist[4] = 4
- 找到了未标识且离源点
1
最近的结点4
,标识4
号点,用4
号点更新其他所有点的距离,5
号点被更新成dist[5] = 5
- 找到了未标识且离源点
1
最近的结点3
,标识3
号点,用3
号点更新其他所有点的距离,4
号点被更新成dist[4] = 3
- 结束
- 得到
1
号点到5
号点的最短距离是5,对应的路径是1 -> 2 -> 4 -> 5
,并不是真正的最短距离
bellman-ford 算法思想
Bellman - ford 算法是求含负权图的单源最短路径的一种算法,效率较低。遍历k次(这个k是路径上经过的边的数量);每一次遍历,会遍历所有边,同时更新所有边的距离。
为什么要使用backup数组
更新距离的代码如下:
for (int i = 0; i < k; i ++ )
{
memcpy(backup, dist, sizeof dist);
for (int j = 0; j < m ; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b], backup[a] + w); // 这里要使用backup数组防止串联
}
}
串联的例子如下:(即:当k = 1时,1到3的距离应该是无穷大,但如果不使用backup数组,那么就会发生串联(3的距离就会变成2))
为什么是dist[n]>0x3f3f3f3f/2
, 而不是dist[n]>0x3f3f3f3f
因为最后一个可能是无穷大减去若干个负数
完整代码
#include <cstring>
#include <algorithm>
#include <algorithm>
using namespace std;
const int N = 510, M = 10010;
int n, m, k;
int dist[N], backup[N];
struct Edge
{
int a, b ,w;
}edegs[M];
int bellman_ford()
{
// 初始化
memset(dist, 0x3f3f3f3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; ++i)
{
// 只用上一次迭代的结果,那么就不会出现串联了
// 1. backup数组防止串联
memcpy(backup, dist, sizeof dist);
for (int j = 0; j < m; ++j)
{
int a = edegs[j].a, b = edegs[j].b, w = edegs[j].w;
dist[b] = min(dist[b], backup[a] + w);
}
}
// 这里不是无穷大, 而是比无穷大要小
if (dist[n] > 0x3f3f3f3f / 2) return -1;
return dist[n];
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < m; ++i)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edegs[i] = {a, b, w};
}
int t = bellman_ford();
if (t == -1) // 说明最短路长度不存在
puts("impossible");
else printf("%d\n", t);
return 0;
}