853. 有边数限制的最短路
Bellman-Ford算法
算法思路:
for n次
for 所有边 a,b,w 表示存在一条a->b 的边,权重是 w
dist[b]=min(dist[b],dist[a]+w)
结束!
概念:了解即可
对于所有的边都满足 dist[b]<=dist[a]+w ,这个叫三角不等式,更新操作叫松弛操作。
Bellman_Ford适用于负权边的,
有负权回路的话,最短路是不一定存在的。为什么是不一定,可以看图1和图2.
图1描述了一直走下去一直小,那么,最短路径就是负无穷,不存在最短路径。
图2描述了虽然有负权回路,但由于此负权回路不在1~n的路线中,不影响最短路,所以,也还存在最短路径。
Bellman_Ford是可以找负环的,但时间复杂度比较高,一般不用它来找负环。
Bellman_Ford能干的事,SPFA都能干,而且时间复杂度比Bellman_Ford要低。
但有一个例外,就是如果限制了边数的最短路,那么只能用Bellman_Ford算法。
算法逻辑
1)初始化所有点到源点的距离为∞,把源点到自己的距离设置为0;
2)不管3721遍历n次;每次遍历m条边,用每一条边去更新各点到源点的距离。
值得注意的是
-
⭐️需要把dist数组进行一个备份,这样防止每次更新的时候出现串联;
-
⭐️由于存在负权边,因此return -1的条件就要改成dist[n]>0x3f3f3f3f/2;
-
⭐️上面所谓的n次遍历的实际含义是当前的最短路径最多有n-1条边,这也就解释了为啥要i遍历到n的时候退出循环了,因为只有n个点,最短路径无环最多就存在n-1条边。
-
⭐️这里无需对重边和自环做单独的处理:
1] 重边:由于遍历了所有的边,总会遍历到较短的那一条; 2] 自环: 有自环就有自环啊,反正又不会死循环; -
⭐️令人愉悦的是,该算法无非就是循环n次然后遍历所有的边,因此不需要做什么特别的存储,只要把所有的边的信息存下来能够遍历就行;
6)⭐️bellman_ford算法可以存在负权回路,因为它求得的最短路是有限制的,是限制了边数的,这样不会永久的走下去,会得到一个解;
- ⭐️SPFA算法各方面优于该算法,但是在碰到限制了最短路径上边的长度时就只能用bellman_ford了,此时直接把n重循环改成k次循环即可
理解与感悟
(1)本题明确存在负权边,所以不能使用Dijkstra算法,Dijkstra只能用在正权边。
(2)有边数限制的最短路径
bellman - ford算法擅长解决有边数限制的最短路问题
这个有边数限制就很霸气了,其它算法没有这个功能!
比个现实中的场景:旅游N个城市,1->n没有直达的飞机,我们需要经其它的城市进行中转,
票价就是中转的机票票价之和,每次中转都会让人的心情不爽一点,也就是不能超过k次中转。
(3)Bellman_ford是可以用来判断负环的,但由于时间复杂度较高,一般使用SPFA来判断负环。
(4)什么情况下存在负环,还存在最短路径?
比如
上面这张图之所以不存在负环,是因为2号点不在1->n点的路径上,就是有负环,也无谓!
(5)、为什么需要backup[a]数组
为了避免如下的串联情况, 在边数限制为一条的情况下,节点3的距离应该是3,但是由于串联情况,利用本轮更新的节点2更新了节点3的距离,所以现在节点3的距离是2。
正确做法是用上轮节点2更新的距离--无穷大,来更新节点3, 再取最小值,所以节点3离起点的距离是3。
C++ 代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, M = 10010;
//bellman_ford算法可以使用结构体,不用使用邻接表,很牛B,为什么Dijkstra不行呢?
struct Edge {
int a, b, w; //起点,终点和权重
} edges[M];
int n, m, k;
int dist[N];
int backup[N];
int bellman_ford() {
//初始化
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i++) { //不超过k条边的最短距离
//把dist数组备份一下,只用上一次迭代的结果,防止出现串联,因为本次迭代可以造成几个节点数据的修改
memcpy(backup, dist, sizeof dist);
for (int j = 0; j < m; j++) {
auto e = edges[j];
dist[e.b] = min(dist[e.b], backup[e.a] + e.w);
}
}
return dist[n];
}
int main() {
//输入
scanf("%d%d%d", &n, &m, &k); //不超过k次
for (int i = 0; i < m; i++) {
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
//调用bellman_ford算法
int res = bellman_ford();
//为什么要用dist[n]>0x3f3f3f3f/2? 这可能是因为在操作过程中dist[n]被更新成0x3f3f3f3f小一点的数,其实还是无法到达
if (res > 0x3f3f3f3f / 2) puts("impossible");
else printf("%d\n", res);
return 0;
}
原文地址:https://www.cnblogs.com/littlehb/p/15319764.html
- 利用Python实现DGA域名检测
- 一个“爆款”成功的API,都离不开这8条设计准则
- RavenDb学习(八)高级特性上半部分
- 微信钱包中58到家首页为什么这么快
- hbase源码系列(二)HTable 探秘
- hbase源码系列(三)Client如何找到正确的Region Server
- hbase源码系列(五)Trie单词查找树
- 如何在特定的渗透测试中使用正确的Burp扩展插件
- hbase源码系列(十一)Put、Delete在服务端是如何处理?
- 大数据如何帮飞机节油?
- hbase源码系列(十二)Get、Scan在服务端是如何处理?
- OpenStack:建立虚拟的渗透测试实验环境 – 网络篇
- Kettle 添加对应hadoop版本的支持
- Layui常用方法
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- django实现后台显示媒体文件
- 服务发现与消费--Eureka与Ribbon的联手出击
- 自我加戏,在自闭的边缘尝试高可用的Eureka
- 视频直播系统源码,图片叠加
- SpringCloud初体验--Hello Eureka
- Android自定义View实现水平带数字百分比进度条
- AndResGuard编译速度优化
- Android自定义带拼音音调Textview
- Android仿音乐播放器带进度的播放暂停按钮
- 一个比较自闭的SpringIOC问题
- 我在大厂写React,学到了什么?
- leetcode(4)寻找正序数组中位数
- jvm源码解析(二)HashMap
- 硬件笔记(23)---- PCB的保护走线
- TabLayout+ViewPager实现切页的示例代码