当前位置: 首页 > news >正文

【算法磨剑:用 C++ 思考的艺术・单源最短路收官】BF/SPFA 负环判断模板 + 四大算法全总结

在这里插入图片描述
在这里插入图片描述

文章目录

  • 前言:
    • 《算法磨剑: 用C++思考的艺术》 专栏
    • 《C++:从代码到机器》 专栏
    • 《Linux系统探幽:从入门到内核》 专栏
  • 正文:
    • BF 算法判断负环
      • 代码实现:
    • spfa 算法判断负环
      • 代码实现:
    • 单源最短路算法总结:
    • [P1629 邮递员送信](https://www.luogu.com.cn/problem/P1629) [P1744 采购特价商品](https://www.luogu.com.cn/problem/P1744) [P2136 拉近距离](https://www.luogu.com.cn/problem/P2136) [P1144 最短路计数](https://www.luogu.com.cn/problem/P1144)
  • 结语:

前言:

《算法磨剑: 用C++思考的艺术》 专栏

专注用C++破解算法面试真题,详解LeetCode热题,构建算法思维,从容应对技术挑战。

👉 点击关注专栏


《C++:从代码到机器》 专栏

深入探索C++从语法特性至底层原理的奥秘,构建坚实而深入的系统编程知识体系。

👉 点击关注专栏


《Linux系统探幽:从入门到内核》 专栏

深入Linux操作系统腹地,从命令、脚本到系统编程,探索Linux的运作奥秘。

👉 点击关注专栏


作者:孤廖
学习方向:C++/Linux/算法
人生格言:折而不挠,中不为下

正文:

P3385 【模板】负环

借助该题 讲述bf算法,spfa算法对带有负环的有向图的单源最短路问题模板的讲述
在这里插入图片描述
在这里插入图片描述

【解法】

如果图中存在负环,那么有可能不存在最短路。
在这里插入图片描述

因为 会在负环中一直走下去,找不到最小路。

BF 算法判断负环

  • 执⾏ n 轮松弛操作,如果第 n 轮还存在松弛操作,那么就有负环

解释:如果图中存在负环则会一直进行松弛操作 ,这里我们只需判断第n轮是否进行松弛操作即可

代码实现:

#define _CRT_SECURE_NO_WARNINGS
//bf判断负环模板#include <iostream>
#include <cstring>
using namespace std;
const int M = 3e3 + 10,N=2e3+10;
int T, n, m;//多组数据T ,点数,边数
struct edges
{int u, v, z;//u~v 的权值z
}e[M*2];
int dist[N];//dist[i]:节点i 距离起始点1的最小路径
int pos;//实际边的条数
bool bf()
{//初始化memset(dist, 0x3f3f3f3f, sizeof dist);dist[1] = 0;//bf算法bool falg;//标记此轮是否有松弛操作for (int i = 1; i <= n; i++)//如果有环松弛的轮数超过n-1{falg = false;//遍历所有边for (int i = 1; i <= pos; i++){int u = e[i].u, v = e[i].v, z = e[i].z;if (dist[u] == 0x3f3f3f3f) continue;//前面的点还没松弛到u//判断是否进行松弛操作if (dist[u] + z < dist[v]){//松弛操作dist[v] = dist[u] + z;falg = true;}}if (!falg) return falg;//当前轮没有松弛操作 说明所有点 //单源最小路径已经确定 没有负环}return falg;//第n轮仍然进行松弛操作 说明有负环
}
int main()
{cin >> T;while (T--){pos = 0;//处理多组数据,数据覆盖清楚上一组数据//处理边的信息cin >> n >> m;for (int i = 1; i <= m; i++){int u, v, z;cin >> u >> v >> z;pos++;e[pos].u = u, e[pos].v = v, e[pos].z = z;//u~v//判断是否为双向边if (z >= 0){pos++;e[pos].u = v, e[pos].v = u, e[pos].z = z;//v~u}}//输出结果if (bf()) cout << "YES" << endl;else cout << "NO" << endl;}return 0;
}

spfa 算法判断负环

维护⼀个 cnt 数组记录从起点到该点所经过的边数,如果 cnt[i] >= n ,说明有负环。

解释:spfa算法是运用stl中的队列对bf算法的选边操作进行的优化。在代码实现上每次从队列中拿取之前松弛过的边 如果存在负环话 队列始终有元素 会死循环 因此相对bf算法通过控制循环次数来判断是否有负环,spfa可以采用cnt数组记录每个点到起始点的路径上经过的边数来判断是否有负环

代码实现:

#define _CRT_SECURE_NO_WARNINGS
//spfa 判断负环模板
#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int N=2e3+10;
typedef pair<int, int> PII;
vector<PII> edges[N];
int T, n, m;//多组数据T ,点数,边数
int dist[N];//dist[i]:节点i 距离起始点1的最小路径
bool st[N];//st[i]:节点i是否被松驰过已经放在队列里 避免重复对一个点松弛
//因为spfa算法用的队列对拿到松弛边的优化 如果存在负环的话 就死循环了
//所以用cnt数组来判断 每个点到达起源点的边数是否超过不存在
//负环情况下的最大边数n-1
int cnt[N];//cnt[i]:表示起始点1到点i经历的边数
bool spfa()
{//初始化//多组数据记得清空上一组数据memset(dist, 0x3f3f3f3f, sizeof dist);memset(st, 0, sizeof st);memset(cnt, 0, sizeof cnt);dist[1] = 0;queue<int> q;q.push(1);//源点入队st[1] = true;//遍历所有被松弛过的点边while (q.size()){//拿到边的信息int u = q.front(); q.pop();st[u] = false;//出队for (auto& e : edges[u]){int v = e.first, z = e.second;//判断是否松弛if (dist[u] + z < dist[v]){//松弛dist[v] = dist[u] + z;cnt[v] = cnt[u] + 1;//dp//判断是否有环if (cnt[v] >= n) return true;//有负环//判断是否入队 避免对一个点的出边进行重复松弛if (!st[v]){q.push(v);st[v] = true;}}}}return false;//无负环
}int main()
{cin >> T;while (T--){cin >> n >> m;//多组数据 清空当前组所需空间的数据for (int i = 1; i <= n; i++)  edges[i].clear();//存储边的信息for (int i = 1; i <= m; i++){int u, v, z;cin >> u >> v >> z;//u~vedges[u].push_back({ v,z });//判断是否有双边if (z >= 0) edges[v].push_back({ u,z });//v~u}//输出结果if (spfa() ) cout << "YES" << endl;else cout << "NO" << endl;}return 0;
}

单源最短路算法总结:

注意注意注意:
所有的模板学完之后,希望⼤家先不要着急去刷题,⽽是花上⼀段时间消化⼀下上述所有的最短路算法

  • 重点不是去记忆代码,⽽是去记忆每⼀个最短路算法的流程。把纸和笔拿出来,⾃⼰给⾃⼰讲⼀遍
    某个最短路算法。⼀定要把基础打好再去刷题
  • 不要偷懒,只想着记住⼀个就完事了。题⽬是灵活的,我们如果掌握多个最短路算法,也是可以灵活应对的。
常规版 - dijkstra堆优化 - dijkstrabellman - ford 算法spfa 算法
算法思想贪心

- 每次拿出还未确定最短路的点中,距离起点最近的点;

- 打上标记之后,更新出边所连点的最短路。
使用堆优化找点操作:

- 把还未确定最短路的点扔到堆中,用堆快速找出距离起点最近的点。
暴力松弛

- 执行 n - 1 轮松弛操作;

- 每次都扫描所有的边,看看能否松弛。
使用队列优化 bf 算法:

- 只有上一轮被松弛的点,下一轮才有可能松弛。
负边权失效失效可行可行
负环失效失效可以判断负环:

- 执行 n 轮操作,判断是否松弛
可以判断负环:

- 创建 cnt 数组,标记从起点到该点的边数
时间复杂度O(n2)O(n^2)O(n2)O(mlog⁡m)O(m\log m)O(mlogm)O(nm)O(nm)O(nm)O(km)∼O(nm)O(km) \sim O(nm)O(km)O(nm)

其实还有两个单源最短路算法,那就是普通 bfs 以及 01bfs:
• 普通 bfs 只能处理边权全部相同且⾮负的最短路;
• 01bfs 只能解决边权要么为 0,要么为 1 的情况

大家只要将上述四种算法流程熟记于心,吃透模板,那么大多数的单源最短路问题都可以解决,不同问题代码要会灵活改动,具体问题具体分析~

以下是我为大家找到的四道应用题,在吃透模板后可以自己加以练习,本文就不在此些问题做代码实现了。但是如果想要交流的话可以私信。

P1629 邮递员送信
P1744 采购特价商品
P2136 拉近距离
P1144 最短路计数




结语:

这篇 “算法磨剑”,咱们终于给单源最短路系列画上了句号 —— 不仅补全了 BF 和 SPFA 的 “负环判断” 模板(BF 靠 “n 轮后仍能松弛”、SPFA 凭 “节点入队超 n 次”,两种思路各有适配场景),还把 Dijkstra(朴素 / 堆优化)、BF、SPFA 这四大算法的适用场景、时间复杂度、核心优劣做了系统总结,形成完整的 “算法工具箱”。

从 “非负权图用 Dijkstra” 到 “负权边靠 BF/SPFA”,再到 “负环检测补全边界场景”,整个系列其实在讲一个逻辑:算法选择的本质是 “问题场景与效率的匹配”比如稀疏图选堆优化 Dijkstra,含负权但无负环用 SPFA,需限制路径边数就用 BF—— 吃透这点,面对不同题目才能快速 “对号入座”。

如果这篇总结帮你理清了单源最短路的知识脉络,不妨点个赞 + 收藏,方便后续刷题时快速查阅;也欢迎在评论区交流:你觉得哪个算法的负环判断逻辑最容易记?刷题时有没有遇到过 “四大算法都能解,但效率天差地别” 的题目?接下来专栏会转向 “多源最短路” 或 “最小生成树进阶”,咱们继续在算法的世界里 “磨剑”~

http://www.dtcms.com/a/388830.html

相关文章:

  • Flink的介绍及应用
  • 微信小程序插屏广告(InterstitialAd)全解析与实战应用案例
  • 格雷希尔G70R系列快速密封连接器+GT系列软管组件的配套组合方案,在新能源汽车老化测试的应用
  • 【Debug日志| 随机下降】
  • 滑动窗口法的优化与实战——力扣209.长度最小的子数组
  • 【Spring Boot 报错已解决】org.yaml.snakeyaml.scanner.ScannerException 报错原因与解决方案
  • 国家统计局数据读取——数据读取——清洗数据06
  • 基于 scratch 构建简单镜像
  • Web安全的暗角:10大易忽略逻辑漏洞解析!
  • 矩阵奇异值分解算法(SVD)详解
  • 【FreeRTOS】 二值信号量与互斥量(CMSIS-RTOS v2 版本)
  • Qt C++ :Qt全局定义<QtGlobal>
  • 【STL源码剖析】从源码看 list:从迭代器到算法
  • MySQL 专题(三):事务与锁机制深度解析
  • 使用BLIP训练自己的数据集(图文描述)
  • Geoserver修行记--在geoserver中如何复制某个图层组内容
  • DBG数据库透明加密网关:SQLServer应用免改造的安全防护方案,不限制开发语言的加密网关
  • 不同上位开发语言、PLC下位平台、工业协议与操作系统平台下的数据类型通用性与差异性详解
  • 【入门篇|第二篇】从零实现选择、冒泡、插入排序(含对数器)
  • javaweb Servlet基本介绍及开发流程
  • MySQL MHA高可用
  • 整体设计 逻辑拆解之2 实现骨架:一元谓词+ CNN的谓词系统
  • SpEL(Spring Expression Language)学习笔记
  • Java 字节码进阶3:面向对象多态在字节码层面的原理?
  • Tensor :核心概念、常用函数与避坑指南
  • 机器学习实战·第四章 训练模型(1)
  • 一次因表单默认提交导致的白屏排查记录
  • Linux:io_uring
  • 《第九课——C语言判断:从Java的“文明裁决“到C的“原始决斗“——if/else的生死擂台与switch的轮盘赌局》
  • 学习日报|Spring 全局异常与自定义异常拦截器执行顺序问题及解决