图论 _ 含负权边的最短路
松弛:
-
考虑节点u以及它的后继节点v。从起点跑到v有好多跑法,有的跑法经过u,有的不经过。
-
经过u的跑法的距离就是。
-
松弛操作就是比较和哪个大。
-
如果前者大一点,就说明当前的不是最短路,就要赋值为后者,这就叫做松弛。
Bellman - Ford算法
Bellman - Ford算法用于求带负权值的图的最短路
时间复杂度
一般来说spfa算法各项性能都是优于Bellman - Ford算法。
只有个别题(后面应用的时候会说),只能用Bellman - Ford算法来做。
算法理解
伪代码:
for n次 for 所有边(a,b,w) dist[b] = min(dist[b],last[a] + w)//last是指上一次循环的dist
解释:
假设 1 号点到 n 号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,
- 通过循环 n-1 次操作,若图中不存在负环,则 1 号点一定会到达 n 号点,
- 若图中存在负环,则在 n-1 次松弛后一定还会更新
如果图中有负环,则无法得出结果,否则就得到最短路。
这个博客有图解:https://www.cnblogs.com/gaochundong/p/bellman_ford_algorithm.html
看完图解应该就明白了,很简单算法。
然后有个问题是为什么使用last,而不直接用dist更新?
为了避免串联:
如下图:在边数限制为一条的情况下,节点3的距离应该是3,
- 如果 利用本轮更新的节点2更新了节点3的距离,所以现在节点3的距离是2,结果错误。
- 如果用上轮的dist(就是last)更新j就没这个问题。
代码
struct Edge{ int a,b,w; }edges[N]; int n, m,k; int dist[N]; int last[N]; void Bellman_Ford(){ memset(dist,0x3f,sizeof dist); dist[1]=0; for(int i=0;i<k;i++){ memcpy(last,dist,sizeof dist); for(Edge e : edges){ dist[e.b]=min(dist[e.b],last[e.a]+e.w); } } } void solve() { cin >> n >> m>>k; for (int i = 0; i < m; i++) { cin>>edges[i].a>>edges[i].b>>edges[i].w; } Bellman_Ford(); if (dist[n] > 0x3f3f3f3f / 2) puts("impossible"); else printf("%d\n", dist[n]); }
为什么判断结果存在是:dist[n]>0x3f3f3f3f/2, 而不是dist[n]>0x3f3f3f3f
比如说:只有节点x到终点en,而且这条边的权值是负数;
如果起始点be到节点x的距离是无穷大(0x3f3f3f3f),那么起始点be到终点en最短路的权值是小于0x3f3f3f3f的,但是实际上并不存在最短路,所以判断条件不能是0x3f3f3f3f。
应用
一般来说spfa算法各项性能都是优于Bellman - Ford算法。
bellman - ford算法擅长
1.有边数限制的最短路问题
2.带负环的最短路问题
负环:
如果图中存在负环,则最短路不一定存在。
因为如果在负环中一直转,则最短路长度是负无穷...
但是如果负环不能到达终点,或者题目限制最短路的长度,则负环存在也不影响最短路。
负环让最短路变成负无穷的关键是:在负环中一直转
因为Bellman - Ford算法其循环的次数是有限制的(就是n次循环),所以对负环免疫。
SPFA算法
SPFA算法是Bellman ford算法的一个优化。
复杂度:
一般情况下算法复杂度是
但是最坏情况下和Bellman ford算法一样,都是.
算法理解
Bellman_ford算法每次会遍历所有的边,但是很多的遍历没有意义,SPFA就是每次只遍历有意义的边。
我们可以想到: 有意义的的遍历只有那些到源点距离变小的点。
因为只有当一个点的前驱结点更新了,该节点才会得到更新;
所以我们创建一个队列将距离被更新的结点加入队列,再用加入队列的节点重复发上述操作。
代码
请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible。
int n, m; int to[M], w[M], pre[M], h[N], idx; void add(int a, int b, int c) { to[idx] = b, w[idx] = c, pre[idx] = h[a], h[a] = idx++; } int vis[N], dist[N]; void SPFA(int be, int en) { memset(dist, 0x3f, sizeof dist); queue<int> q; q.push(be); dist[be] = 0, vis[be] = true; while (q.size()) { int k = q.front(); q.pop(); vis[k] = false; for (int i = h[k]; i != -1; i = pre[i]) { int j = to[i]; if (dist[j] > dist[k] + w[i]) { dist[j] = dist[k] + w[i]; if (!vis[j]) { vis[j] = true; q.push(j); } } } } if (dist[en] >= 0x3f3f3f3f) cout << "impossible" << endl; else cout << dist[en] << endl; } void solve() { memset(h, -1, sizeof h); cin >> n >> m; while (m--) { int a, b, c; cin >> a >> b >> c; add(a, b, c); } SPFA(1, n); }
为什么Bellman_ford算法里最后return-1的判断条件写的是dist[n]>0x3f3f3f3f/2;而spfa算法写的是dist[n]==0x3f3f3f3f?
因为:
- Bellman_ford算法会遍历所有的边,因此不管是不是和源点连通的边它都会得到更新;
- 但是SPFA只会遍历与源点连通的节点,因此如果终点和源点不连通,它不会得到更新,还是保持的0x3f3f3f3f。
应用
Bellman_ford算法可以在负权回路的图上跑。,是因为其循环的次数是有限制的(就是n次循环)
但是SPFA算法不可以,由于用了队列来存储,只要发生了更新就会不断的入队,陷入死循环。
-
也能解决权值为正的图的最短距离问题,且一般情况下比Dijkstra算法还好
(但是不要尝试,出题人可能会设计特殊样例卡掉) -
求含负权边的单源最短路
3. 求负环
方法:
用一个cnt数组记录每个点到源点的边数,一个点被更新一次就+1,一旦有点的边数达到了n那就证明存在了负环。
代码:
int n, m; int to[M], w[M], pre[M], h[N], idx; void add(int a, int b, int c) { to[idx] = b, w[idx] = c, pre[idx] = h[a], h[a] = idx++; } int vis[N], dist[N]; int cnt[N]; bool SPFA(int be, int en) { // memset(dist, 0x3f, sizeof dist); queue<int> q; //因为负环不一定和起始点相连 ,所以开始将所有点都放入队列 for (int i = 1; i <= n; i++) { vis[i] = true; q.push(i); } while (q.size()) { int k = q.front(); q.pop(); vis[k] = false; for (int i = h[k]; i != -1; i = pre[i]) { int j = to[i]; if (dist[j] > dist[k] + w[i]) { dist[j] = dist[k] + w[i]; cnt[j] = cnt[k] + 1; if (cnt[j] >= n) return true; if (!vis[j]) { vis[j] = true; q.push(j); } } } } return false; } void solve() { memset(h, -1, sizeof h); cin >> n >> m; while (m--) { int a, b, c; cin >> a >> b >> c; add(a, b, c); } if (SPFA(1, n)) puts("Yes"); else puts("No"); }
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/16555604.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步