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

【算法磨剑:用 C++ 思考的艺术・单源最短路进阶】Bellman-Ford 与 SPFA 算法模板精讲,突破负权边场景

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

文章目录

  • 前言:
    • 《算法磨剑: 用C++思考的艺术》 专栏
    • 《C++:从代码到机器》 专栏
    • 《Linux系统探幽:从入门到内核》 专栏
  • 正文:
    • 一:bellman-ford 算法
      • 介绍:
      • Bellman‒Ford 算法流程:
      • 代码实现:
    • 二: spfa 算法
      • 介绍:
      • spfa 算法流程:
      • 代码实现:
  • 结语:

前言:

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

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

👉 点击关注专栏


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

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

👉 点击关注专栏


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

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

👉 点击关注专栏


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

正文:

继上篇 Dijkstra 算法后,本文带来能处理负权边的 Bellman - Ford 与优化版 SPFA 算法模板精讲~需注意:负环的判断技巧,我们留到下一篇博客详细讲解。

一:bellman-ford 算法

介绍:

Bellman‒Ford 算法(之后简称 BF 算法)是⼀种基于松弛操作的最短路算法,可以求出有负权的图的最短路,并可以对最短路不存在的情况进⾏判断
算法核⼼思想:不断尝试对图上每⼀条边进⾏松弛,直到所有的点都⽆法松弛为⽌。
在这里插入图片描述

Bellman‒Ford 算法流程:

  • 准备⼯作:
  • 创建⼀个⻓度为 n 的 dist 数组,其中 dist[i] 表⽰从起点到 i 结点的最短路
  • 初始化: dist[1] = 0 ,其余结点的 dist 值为⽆穷⼤,表⽰还没有找到最短路。
  • 重复:每次都对所有的边进⾏⼀次松弛操作
  • 重复上述操作,直到所有边都不需要松弛操作为⽌。

最多重复多少轮松弛操作?

  • 在最短路存在的情况下,由于⼀次松弛操作会使最短路的边数⾄少增加 1,⽽最短路的边数最多为 n -1 因此整个算法最多执⾏轮松弛操作 n - 1 轮。故总时间复杂度为 O(nm) 。

代码实现:

#define _CRT_SECURE_NO_WARNINGS
//bellman-ford 算法#include <iostream>
#include <vector>
using namespace std;
const int N = 1e4 + 10, INF = 2147483647;
typedef pair<int, int> PII;
vector<PII> edges[N];//edges[i]:i的出边点  和其对应的边权值
int n,m,s;//点,边的个数,起源点
int dist[N];//dist[i]:节点i距离起源点的最短路径
void bellman_ford()
{//初始化for (int i = 0; i <= n; i++) dist[i] = INF;dist[s] = 0;//bfbool flag = false;//标记该轮是否进行过松弛操作for (int i = 1; i < n; i++)//每次松弛一个边 最多n-1次{flag = false;//遍历所有点for (int i = 1; i <= n; i++){if (dist[i] == INF) continue;//避免INF 溢出 成为负数//将所有点的出边进行松弛操作for (auto& e : edges[i]){int v = e.first, z = e.second;if (dist[i] + z < dist[v]){dist[v] = dist[i] + z;flag = true;}}}if (flag == false) break;}//输出结果for (int i = 1; i <= n; i++){cout << dist[i] << " ";}
}

二: spfa 算法

介绍:

spfa 即 Shortest Path Faster Algorithm,本质是⽤队列对 BF 算法做优化。
在 BF 算法中,很多时候我们并不需要那么多⽆⽤的松弛操作:

  • 只有上⼀次被松弛的结点,它的出边,才有可能引起下⼀次的松弛操作;
  • 因此,如果⽤队列来维护"哪些结点可能会引起松弛操作",就能只访问必要的边了,时间复杂度就能降低

spfa 算法流程:

  • 准备⼯作:
  • 创建⼀个⻓度为 n 的 dist 数组,其中 dist[i] 表⽰从起点到 i 结点的最短路;
  • 创建⼀个⻓度为 n 的 bool 数组 st ,其中 st[i] 表⽰ i 点是否已经在队列中。
  • 初始化:标记 dist[1] = 0 ,同时 1 ⼊队;其余结点的 dist 值为⽆穷⼤,表⽰还没有找到最短路.
  • 重复:每次拿出队头元素 u ,去掉在队列中的标记,同时对 u 所有相连的点 v 进⾏松弛操作。如果结点 v 被松弛,那就放进队列中。
  • 重复上述操作,直到队列中没有结点为⽌

注意注意注意:
虽然在⼤多数情况下 spfa 跑得很快(最优时间复杂度可以到o(Km))(K是一个常数),但其最坏情况下的时间复杂度为 O(nm)。将其卡到这个复杂度也是不难的,所以在没有负权边时最好使⽤ Dijkstra 算法。

如何优雅的卡spfa 感兴趣的可以看下。

代码实现:

#define _CRT_SECURE_NO_WARNINGS
//spfa 算法 本质是用队列对bellman-ford算法的优化#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int N = 1e4 + 10, INF = 2147483647;
typedef pair<int, int> PII;
vector<PII> edges[N];//edges[i]:i的出边点  和其对应的边权值
int n, m, s;//点,边的个数,起源点
int dist[N];//dist[i]:节点i距离起源点的最短路径
bool st[N];//st[i]:节点i 是否在队列中
void spfa()
{//初始化for (int i = 0; i <= n; i++) dist[i] = INF;dist[s] = 0;queue<int> q;//将上一轮进行过松弛操作的点加入队列中q.push(s);st[s] = true;//spafwhile (q.size()){int u = q.front();q.pop();st[u] = false;//将 u 的出边进行松弛操作for (auto& e : edges[u]){int v = e.first;int z = e.second;if (dist[u] + z < dist[v]){dist[v] = dist[u] + z;if (!st[v]) q.push(v);}}}//输出结果for (int i = 1; i <= n; i++) cout << dist[i] << " ";}
int main()
{cin >> n >> m >> s;for (int i = 1; i <= m; i++){int u, v, z;cin >> u >> v >> z;edges[u].push_back({ v,z });}spfa();return 0;
}



结语:

这篇 “算法磨剑:用 C++ 思考的艺术”,我们把Bellman-Ford和它的 “优化形态”SPFA在 “负权边场景” 里彻底 “磨” 通了 ——Bellman-Ford 靠 “k 轮全边松弛”,天生能处理含负权的图(还能限制最短路最多经过 k 条边),用 C++ 的memcpy做 “备份数组”,让 “松弛的顺序干扰” 变得可控;SPFA 则用队列优化,只对 “可能被更新的节点” 重复松弛,靠 C++ 的queue把时间复杂度从 “稳定 O (nm)” 优化到 “多数情况接近 O (m)”,完美适配 “非极端负权图” 的实战。

但这俩算法的故事还没结束 —— 它们能处理负权边,却也天生和 “负环”(能无限缩短路径的环)深度绑定:Bellman-Ford 能通过 “是否还能松弛” 判断负环存在,SPFA 更能高效定位负环。明天的专栏,我们就聚焦 “负环判断”,用具体例题看这俩算法如何从 “处理负权” 进阶到 “检测致命负环”,补上单源最短路的最后一块拼图。

如果今天的两个算法模板解析,帮你突破了 “Dijkstra 处理不了负权” 的瓶颈,不妨点个赞 + 收藏方便复习;也欢迎评论区交流:你学 Bellman-Ford 时,有没有被 “k 轮松弛” 的逻辑绕晕?用 SPFA 时,有没有遇到过 “队列优化反而变慢” 的极端场景?带着对 “负环” 的好奇,我们明天继续 “磨” 单源最短路的收尾技巧~


文章转载自:

http://IyziZK9W.rfqkx.cn
http://FFbUbqpg.rfqkx.cn
http://nAqqIwaj.rfqkx.cn
http://2HnjW2wb.rfqkx.cn
http://eTjAqvoT.rfqkx.cn
http://s96AwUSu.rfqkx.cn
http://Qnzc9QqH.rfqkx.cn
http://iLryMMEK.rfqkx.cn
http://GnpjfRZc.rfqkx.cn
http://9uR9T7jE.rfqkx.cn
http://LSX0JpSQ.rfqkx.cn
http://gWcciJTI.rfqkx.cn
http://54RGbaPU.rfqkx.cn
http://3QoHwbFC.rfqkx.cn
http://RabtUouF.rfqkx.cn
http://MC6jkzRd.rfqkx.cn
http://jqwCupnO.rfqkx.cn
http://i4Vnf16b.rfqkx.cn
http://d88OdnHg.rfqkx.cn
http://IZouWBfr.rfqkx.cn
http://HkLdj1P5.rfqkx.cn
http://6n6G2qDc.rfqkx.cn
http://8E4PcisQ.rfqkx.cn
http://sZzImmGa.rfqkx.cn
http://EZytAVDY.rfqkx.cn
http://WqaTgmNW.rfqkx.cn
http://gicW2KyW.rfqkx.cn
http://CN6qp24b.rfqkx.cn
http://w2nJET4M.rfqkx.cn
http://uuqVdMYn.rfqkx.cn
http://www.dtcms.com/a/386655.html

相关文章:

  • 单元测试:驱动模块与桩模块在自顶向下和自底向上的策略中的作用
  • SpringBoot MVC 快速入门
  • Nature Communications 北京大学联合德国马普所在触觉传感器方面取得进展,实现机器人指尖超分辨率力感知
  • 解决一次 “Failed to load model because protobuf parsing failed”:从现象到根因与修复
  • 从ppm到ppb:全面解读浓度单位转换的诀窍
  • 贪心算法应用:霍夫曼编码详解
  • NLP Subword 之 BBPE(Byte-level BPE) 算法原理
  • 【nodejs】Windows7系统下如何安装nodejs16以上版本
  • Part05 数学
  • 每天五分钟深度学习:深层神经网络的优势
  • PCGrad解决多任务冲突
  • 第十一章:游戏玩法和屏幕特效-Gameplay and ScreenEffects《Unity Shaders and Effets Cookbook》
  • Choerodon UI V1.6.7发布!为 H-ZERO 开发注入新动能
  • 科教共融,具创未来!节卡助力第十届浦东新区机器人创新应用及技能竞赛圆满举行
  • 食品包装 AI 视觉检测技术:原理、优势与数据应用解析
  • 【深度学习计算机视觉】05:多尺度目标检测之FPN架构详解与PyTorch实战
  • 从工业革命到人工智能:深度学习的演进与核心概念解析
  • [Emacs list使用及配置]
  • DQN在稀疏奖励中的局限性
  • 为何需要RAII——从“手动挡”到“自动挡”的进化
  • 第五课、Cocos Creator 中使用 TypeScript 基础介绍
  • 09MYSQL视图:安全高效的虚拟表
  • R 语言本身并不直接支持 Python 中 f“{series_matrix}.txt“ 这样的字符串字面量格式化(f-string)语法 glue函数
  • 【AI论文】AgentGym-RL:通过多轮强化学习训练大语言模型(LLM)智能体以实现长期决策制定
  • Win11本地jdk1.8和jdk17双版本切换运行方法
  • vue3 使用print.js打印el-table全部数据
  • Vue 3 + TypeScript + 高德地图 | 实战:多车轨迹回放(点位驱动版)
  • [vue]创建表格并实现筛选和增删改查功能
  • JVM-运行时内存
  • 后缀树跟字典树的区别