多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

D‘Esopo-Pape 算法:基于双端队列的含负边单源最短路径解法(cp-algorithms 实现与测试全解析)

D‘Esopo-Pape 算法:基于双端队列的含负边单源最短路径解法(cp-algorithms 实现与测试全解析) 文档教程知识库【免费下载链接】cp-algorithmsAlgorithm and data structure articles for https://cp-algorithms.com (based on http://e-maxx.ru)项目地址https://gitcode.com/GitHub_Trending/cp/cp-algorithms点击查看免费下载本篇文章以 cp-algorithms 仓库中的 src/graph/desopo_pape.md 为主体系统讲解 DEsopo-Pape也称 Levit算法它面向带负权边、但无负环的图用三集合状态划分配合双端队列deque完成单源最短路径求解在多数场景下比 Dijkstra 算法 与 Bellman-Ford 算法 更快。读完本文你将掌握该算法的三集合标记语义、完整可运行的 C 实现、前驱数组的路径还原方法以及它的最坏情形复杂度陷阱并看到仓库测试用例如何对含负边图进行正确性验证。1. 问题定义与适用边界给定一个包含 $n$ 个顶点、$m$ 条边权重为 $w_i$的图以及一个起始顶点 $v_0$任务是求出从 $v_0$ 到每一个其他顶点的最短路径长度。DEsopo-Pape 算法在大多数情况下会比 Dijkstra 算法 和 Bellman-Ford 算法 运行得更快并且支持负权边。但需要注意其适用边界允许负权边算法不要求边权非负因此比只能处理非负边权的 Dijkstra 适用面更广不允许负权环若图中存在负权环最短路径将无定义长度趋于 $-\infty$算法无法正确收敛这一“能处理负边、不能处理负环”的定位与 Bellman-Ford 算法 的适用场景一致区别在于 Pape 用队列驱动松弛平均性能更好。2. 核心思想三集合状态划分与松弛规则算法维护两个数组$d$当前最短路径长度数组$d_i$ 表示从 $v_0$ 到顶点 $i$ 的当前最短距离。初始时除 $d_{v_0} 0$ 外其余全部为 $\infty$算法结束后 $d$ 即最终答案$p$当前前驱祖先数组$p_i$ 表示从 $v_0$ 到 $i$ 的当前最短路径上 $i$ 的直接前驱。$p$ 与 $d$ 同步逐步更新最终形成完整的最短路径树。算法在每一步维护三个顶点集合集合含义备注$M_0$距离已“计算完成”的顶点但该距离未必是最终距离之后仍可能被改进并退回 $M_1$$M_1$距离“正在计算”的顶点顶点存储在双端队列中$M_2$距离“尚未计算”的顶点初始时除起点外所有顶点都处于该集合集合 $M_1$ 中的顶点存放在一个**双向队列deque**中。算法每步执行如下操作从队列前端取出一个顶点 $u$即当前 $M_1$ 的队首将 $u$ 移入集合 $M_0$遍历从 $u$ 出发的所有边 $(u, v)$权重为 $w$按 $v$ 当前所属集合分三种情况处理若 $v \in M_2$将 $v$插入队列末尾加入 $M_1$并置 $d_v d_u w$若 $v \in M_1$尝试改进 $d_v \min(d_v, d_u w)$。由于 $v$ 已在 $M_1$ 中无需再入队若 $v \in M_0$若 $d_v$ 可被改进即 $d_v d_u w$则更新 $d_v$并把 $v$放回队列开头重新标记为 $M_1$以便尽快重新松弛。每次更新 $d$ 数组时必须同步更新对应的 $p$ 数组元素前驱这与 Dijkstra 算法的路径还原思想 一致。关键设计在于从队首取出、处理完的顶点一旦后续被更短路径再次改进会立即插回队首从而让“被反超”的顶点优先被重新处理。这是该算法区别于 SPFA 等纯 FIFO 队列变体的核心机制也是它平均表现优异的原因。3. 完整 C 实现与逐段解读仓库文档 src/graph/desopo_pape.md 给出了完整实现。为便于对照此处保留原代码并用注释标注关键状态转换m数组即顶点所属集合的标记2对应 $M_2$、1对应 $M_1$、0对应 $M_0$struct Edge { int to, w; }; int n; vectorvectorEdge adj; const int INF 1e9; void shortest_paths(int v0, vectorint d, vectorint p) { d.assign(n, INF); d[v0] 0; vectorint m(n, 2); // 初始所有顶点都在集合 M2尚未计算 dequeint q; q.push_back(v0); // 起点入队并成为 M1 的一员 p.assign(n, -1); while (!q.empty()) { int u q.front(); q.pop_front(); m[u] 0; // u 移入集合 M0距离已计算但可能被后续改进 for (Edge e : adj[u]) { if (d[e.to] d[u] e.w) { // 尝试松弛 d[e.to] d[u] e.w; p[e.to] u; // 同步更新前驱 if (m[e.to] 2) { // M2 - M1插到队尾 m[e.to] 1; q.push_back(e.to); } else if (m[e.to] 0) {// M0 被改进重新入 M1插到队首 m[e.to] 1; q.push_front(e.to); } // 若 m[e.to] 1已在 M1 中仅更新 d 与 p不再入队 } } } }实现要点邻接表表示adj[u]存放从 $u$ 出发的所有边目标顶点to与权重w图既可有向也可无向INF 取值代码中INF 1e9应保证大于任何可能的路径长度。若边权可为负需注意类似 Bellman-Ford 中“从无穷大再松弛”的隐患——不过本算法只在顶点首次入队$M_2 \to M_1$时用精确的 $d_u w$ 直接赋值$M_1$、$M_0$ 分支都要求满足d[e.to] d[u] e.w才更新天然避免了 $\infty - 1$ 这类错误距离的产生可对比 bellman_ford.md 中的相关说明标记数组m一个int数组即可承载三集合语义避免了维护三个独立容器的开销队尾入队 / 队首入队新发现的顶点走push_back被改进的旧顶点走push_front前者遵循广度式扩散后者体现“反超者优先重处理”的优先级。4. 仓库中的验证负边测试用例与自动化构建仓库为本文档提供了可直接运行的单元测试 test/test_desopo_pape.cpp。其构造的图包含负权边顶点 $0 \to 1$ 权重 $-1$负边顶点 $0 \to 2$ 权重 $4$顶点 $1 \to 2$、$1 \to 3$、$1 \to 4$ 权重分别为 $3$、$2$、$2$顶点 $3 \to 2$ 权重 $5$、$3 \to 1$ 权重 $1$顶点 $4 \to 3$ 权重 $-3$负边调用shortest_paths(0, d, p)后测试断言vectorint d_ideal{0, -1, 2, -2, 1}; vectorint p_ideal{-1, 0, 1, 4, 1};其中d的期望值体现了负边松弛的链式改进$d_4 1$经 $0 \to 1 \to 4$后又通过 $4 \to 3$$-3$将 $d_3$ 压到 $-2$前驱记为 $4$。这说明算法对负边带来的“后到先得”式改进能够正确收敛且p数组同步维护无误。关于测试的运行方式仓库在 test/extract_snippets.py 中通过正则^\s*\{.cpp\sfile(\S)\}$从src/目录下所有.md文档中抽取命名代码块例如desopo_pape生成对应的.h头文件供测试源码#include随后 test/test.sh 用g -stdc17逐个编译运行所有*.cpp测试并断言退出码。也就是说本文第 3 节展示的实现片段与测试断言之间形成了“文档代码即被测代码”的闭环任何对算法实现的改动都会在仓库 CI 中得到校验。读者可参考 CONTRIBUTING.md 了解测试的维护约定。5. 复杂度分析与最坏情形警示关于复杂度原文档给出的是经验性结论平均/多数场景算法通常表现相当快多数情况下甚至快于 Dijkstra 算法这是因为队列机制避免了 Dijkstra 在稠密图上重复扫描最小值的开销也避免了 Bellman-Ford 算法 固定 $n-1$ 轮全边扫描的低效最坏情形存在精心构造的图使该算法退化到指数级时间因此它不适合在需要最坏情形时间保证的场景中使用。Stack Overflow 与 Codeforces 上对此有公开讨论可在原文档第 90–92 行的链接中找到结论一致Pape 的算法不是多项式时间算法。由此给出的工程建议场景推荐边权非负、需要稳定性能优先用 Dijkstra稠密图 或 Dijkstra 稀疏图堆优化版复杂度可证明为 $O(n^2 m)$ / $O(n \log n m)$存在负权边、图规模可控且无负环可用 DEsopo-Pape 换取平均速度也可用 Bellman-Ford 保证最坏情形 $O(nm)$需要最坏情形保证勿用 DEsopo-Pape退回到 Bellman-Ford图规模大且含负边可参考 SPFA 测试用例 及其共享的 test/data/sssp.h 数据结构比较各算法表现6. 路径还原与相关算法导航6.1 利用 $p$ 数组还原最短路径算法结束后对任意目标顶点 $t$可沿前驱链从 $t$ 回溯至 $v_0$。仓库中 dijkstra.md 的restore_path实现 展示了通用做法vectorint restore_path(int s, int t, vectorint const p) { vectorint path; for (int v t; v ! s; v p[v]) path.push_back(v); path.push_back(s); reverse(path.begin(), path.end()); return path; }该片段同样适用于本算法的p数组前驱以 $-1$ 表示不存在。6.2 在仓库中的位置与延伸阅读本文档位于仓库图形算法模块的“单源最短路径”系列中参见 src/navigation.md导航中紧随 Dijkstra、稀疏图 Dijkstra 与 Bellman-Ford 之后。建议按如下路径系统学习先掌握 Dijkstra 算法非负边权基准解及其 稀疏图优化再读 Bellman-Ford 算法理解负边与负环的处理框架最后回到本文的 DEsopo-Pape体会“三集合 双端队列”在负边场景下对前两者的加速思想用 test/test_desopo_pape.cpp 的负边用例亲手跑一遍见第 4 节的构建方式验证算法行为。结语DEsopo-Pape 算法在“允许负边、禁止负环”的问题域内用一套优雅的三集合状态机与双端队列调度提供了平均意义上极快的单源最短路径求解。它最大的价值在于用极简的数据结构换来了对 Dijkstra 的普遍反超代价是缺少最坏情形的多项式上界——这正是竞赛与工程实践中需要权衡的核心点。结合本仓库的完整实现、负边测试用例与自动化抽取构建机制读者可以放心地在自己的项目中复现、测试并扩展这一算法。赞分享文档教程知识库【免费下载链接】cp-algorithmsAlgorithm and data structure articles for https://cp-algorithms.com (based on http://e-maxx.ru)项目地址https://gitcode.com/GitHub_Trending/cp/cp-algorithms点击查看免费下载相关推荐Bellman-Ford 算法详解负权边图的最短路径与负环检测 —— 基于 cosmos 多语言实现剖析Bellman Ford 算法详解负权边图的最短路径与负环检测 —— 基于 cosmos 多语言实现剖析 导读 Bellman Ford 算法是图论中求解 单教程示例工程C-Sharp-Algorithms图算法实战5种最短路径算法完全指南C Sharp Algorithms图算法实战5种最短路径算法完全指南 C Sharp Algorithms是一个功能强大的C 算法库提供了标准数据结构和算后端Fan Control 风扇控制到 0 转Fan Control 风扇控制到 0 转 电脑待机、温度 40 度上下风扇还在尖叫。转速控制权在 BIOS 和驱动手里你碰不到。Fan Control 风桌面应用智能硬件上一篇Megatron Core 分布式大模型训练完整指南5 分钟跑通第一个训练循环下一篇Paper 如何用 Access Transformers 修改 Minecraft 类成员可见性创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表