算法完全解析:原理、复杂度与 C++ 实现)
文档教程知识库【免费下载链接】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点击查看免费下载导读Push-relabel预流推进又称 preflow-push算法是求解流网络最大流问题的另一条经典路线。与不断寻找增广路的 Ford-Fulkerson 方法不同它允许顶点临时欠账持有过剩流量通过反复 push 与 relabel 操作把过剩流量推向汇点最终收敛为最大流整体复杂度为 $O(V^2E)$。本文将基于 cp-algorithms 仓库中 push-relabel.md 的完整论述结合仓库内的测试用例与配套的改进版实现帮助你彻底理解该算法的数学基础、两个核心操作的实现细节以及一套可直接编译运行的 C 模板代码。问题背景从最大流到预流最大流问题的完整定义流网络、容量约束、流量守恒、最大流概念参见仓库中同目录的姊妹文章 Maximum flow - Ford-Fulkerson and Edmonds-Karp。简单回顾一个流网络是一个带非负容量函数 $c$ 的有向图 $G(V,E)$并指定源点 $s$ 与汇点 $t$一条流$f$ 需要满足每条边上的流量不超过容量且除源汇外每个顶点的入流等于出流流量守恒。最大流就是取值最大的可行流。Push-relabel 算法由 Andrew Goldberg 与 Robert Tarjan 于 1985 年提出它采用了与增广路方法截然相反的思路维护一个预流而非严格合法的流允许中间顶点暂时积累过剩流量再通过局部推送逐步清空这些过剩量。核心概念预流、过剩量与高度函数预流Preflow预流 $f$ 与流函数类似但不要求满足流量守恒只需满足$$0 \le f(e) \le c(e)$$以及$$\sum_{(v, u) \in E} f((v, u)) \ge \sum_{(u, v) \in E} f((u, v))$$即一个顶点的入流可以大于其出流。我们称该顶点拥有过剩流量excess定义过剩函数为$$x(u) \sum_{(v, u) \in E} f((v, u)) - \sum_{(u, v) \in E} f((u, v))$$与流函数一样可以用预流定义残余容量与残余图。容易看出若一个预流在所有非源汇顶点上的过剩量都为零它就退化为一条合法流——这正是算法终止条件的依据。高度标签Labeling / Height为了既保证算法必然终止又保证最终得到的是一条最大流还需要引入高度函数$h$为每个顶点赋予一个整数。一个标签是合法的当且仅当满足$h(s) |V|$$h(t) 0$对残余图中的每条边 $(u, v)$即残余容量为正的边有 $h(u) \le h(v) 1$。换句话说只要还能从 $u$ 向 $v$ 增加流量$v$ 的高度至多比 $u$ 低 1可以与 $u$ 相等或更高。高度标签有两个关键性质存在合法标签 ⇒ 残余图中不存在 $s$ 到 $t$ 的增广路。因为任何一条简单路径最多有 $|V|-1$ 条边每条边最多让高度下降 1而起点高度 $h(s)|V|$、终点高度 $h(t)0$落差为 $|V|$路径不可能走完。因此算法结束时预流合法 标签合法残余图里已无 $s$-$t$ 路径依据最大流最小割定理当前流量必然是最大流。与 Ford-Fulkerson 的对偶关系这里有一个非常精妙的对称性Ford-Fulkerson始终维护合法流不断改进它直到残余图中不再存在增广路而 push-relabel始终保证不存在增广路通过维护合法标签不断改进预流直到其变为合法流。两者互为对偶殊途同归。算法流程初始化、push 与 relabel初始化不能像 Ford-Fulkerson 那样从全零预流开始——因为此时存在 $s$ 到 $t$ 的增广路也就不存在合法标签。因此算法将每条从源点 $s$ 出发的边直接压满$$f((s, u)) c((s, u))$$其余边流量为 0。此时存在合法标签$h(s) |V|$其余顶点 $h(u) 0$。push 操作push 尝试把一个顶点 $u$ 的过剩流量尽可能推向邻居 $v$。规则只有一条仅当 $h(u) h(v) 1$ 时才能从 $u$ 向 $v$ 推流——过剩流量只能下坡但不能太陡。实际推送量为$$\min(x(u),; c((u, v)) - f((u, v)))$$即取顶点过剩量与边残余容量中的较小者。relabel 操作若顶点 $u$ 有过剩流量但没有任何邻居满足推送条件则需要提升它的高度。提升幅度取满足标签合法性的最大值将 $h(u)$ 设为$$\min{ h(v) : (u,v)\ \text{的残余容量为正} } 1$$算法总览初始化一个合法预流与合法标签反复执行 push 与 relabel直到无法再执行此时预流已无过剩量即为合法流且因标签合法必为最大流返回之。复杂度分析任一顶点的高度上界为 $2|V| - 1$到达该界后所有剩余过剩流量都将被推回源点。由此得到至多 $O(V^2)$ 次 relabel 操作。可以证明至多发生 $O(VE)$ 次饱和推送一条边的容量被完全用尽与 $O(V^2 E)$ 次非饱和推送边容量未被用尽。若用能在 $O(1)$ 时间内取出下一个含过剩顶点的高级数据结构总复杂度为 $O(V^2E)$用满流量记法即 $O(V^4)$在稠密图上两者一致。仓库中的完整 C 实现以下代码直接取自 push-relabel.md代码块标识为filepush_relabel可由 test/extract_snippets.py 自动抽取为push_relabel.h参与编译测试const int inf 1000000000; int n; vectorvectorint capacity, flow; vectorint height, excess, seen; queueint excess_vertices; void push(int u, int v) { int d min(excess[u], capacity[u][v] - flow[u][v]); flow[u][v] d; flow[v][u] - d; excess[u] - d; excess[v] d; if (d excess[v] d) excess_vertices.push(v); } void relabel(int u) { int d inf; for (int i 0; i n; i) { if (capacity[u][i] - flow[u][i] 0) d min(d, height[i]); } if (d inf) height[u] d 1; } void discharge(int u) { while (excess[u] 0) { if (seen[u] n) { int v seen[u]; if (capacity[u][v] - flow[u][v] 0 height[u] height[v]) push(u, v); else seen[u]; } else { relabel(u); seen[u] 0; } } } int max_flow(int s, int t) { height.assign(n, 0); height[s] n; flow.assign(n, vectorint(n, 0)); excess.assign(n, 0); excess[s] inf; for (int i 0; i n; i) { if (i ! s) push(s, i); } seen.assign(n, 0); while (!excess_vertices.empty()) { int u excess_vertices.front(); excess_vertices.pop(); if (u ! s u ! t) discharge(u); } int max_flow 0; for (int i 0; i n; i) max_flow flow[i][t]; return max_flow; }实现要点逐行解读数据结构capacity与flow均为 $n \times n$ 邻接矩阵height、excess、seen分别记录顶点高度、过剩量与 current-arc 游标excess_vertices是一个 FIFO 队列用来 $O(1)$ 取出下一个待处理的过剩顶点。反向边处理push 中flow[v][u] - d维护了残余网络中的反向边——沿反向边推流等价于取消正向流量这是任何最大流算法正确处理回流的关键。excess[s] inf的设计初始化时给源点一个无限过剩量随后对每个非源点push(s, i)即可把所有源点出边一次性压满与理论初始化完全一致。current-arc 优化seen数组discharge中对seen[u]单调递增地遍历邻接顶点形成当前弧数据结构——按循环顺序迭代边并记住最后用到的弧。对于同一高度标签值切换当前弧的总次数为 $O(n)$由于 relabel 本身也是 $O(n)$该优化不会恶化总体复杂度却避免了每次推送都从头扫描邻接表的开销。队列中的顶点筛选max_flow中弹出顶点后先判断u ! s u ! t再执行discharge确保源汇顶点不参与推送。运行方式与测试验证仓库的测试体系可以端到端验证这段代码test/extract_snippets.py 遍历src/下所有 Markdown按 {.cpp file...}标记把代码块抽取为同名.h头文件本文代码会生成push_relabel.h。test/test_push_relabel.cpp 引入该头文件遍历 test/data/flow_networks.h 中预置的 6 个流网络逐一对assert(max_flow(fn.source, fn.sink) fn.maxflow)进行断言校验。test/test.sh 使用g -stdc17 -fsanitizeundefined -fno-sanitize-recover编译并运行全部测试可另设CXX环境变量更换编译器。测试数据涵盖多个经典场景其中尤其值得注意的有第一组即为 cp-algorithms 文章自带的示例网络容量矩阵为 6×6期望最大流 10可直接对照《Edmonds-Karp》一文中的示意图逐步演算第三组是 Wikipedia 中用于说明 Ford-Fulkerson 最坏情形需 2000 次增广的网络——它同样被用来验证 push-relabel 在 $O(V^2E)$ 界下高效运行其余还包括来自 brilliant.org 与 Stanford 课程讲义的网络期望流分别为 23、28、23。如果你修改了算法实现只需重新执行bash test/test.sh全部测试通过即说明实现与已知正确答案一致。进阶阅读最高高度优先的改进版仓库在同一目录提供了对本文算法的一个著名改进push-relabel-faster.md。改进极其简洁不再任意选择过剩顶点而是始终选择高度最高的过剩顶点执行 push / relabel。挑选最高顶点无需复杂数据结构——用一个列表暂存当前最高高度的过剩顶点处理完后重新扫描生成即可。该策略将复杂度大幅优化为 $O(VE V^2\sqrt{E})$最坏情况下为 $O(V^3)$由 Cheriyan 与 Maheshwari 于 1989 年提出。其代码同样可通过测试体系抽取为push_relabel_faster.h并被 test/test_push_relabel_faster.cpp 用同一组flow_networks数据验证。另外push-relabel 也常被用作带需求流问题的求解器见 flow_with_demands.md。总结Push-relabel 算法通过预流 高度标签的双重机制绕开了增广路搜索的瓶颈预流允许过剩流量暂时积累高度标签保证过剩量只能沿下坡方向推送从而以 $O(V^2E)$ 的确定性复杂度求出最大流。本文给出的基于邻接矩阵、配合 current-arc 优化与 FIFO 队列的实现是 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点击查看免费下载相关推荐Flow 索引访问类型Indexed Access Types完全指南T[K] 语法、可选索引访问与 $PropertyType 迁移Flow 索引访问类型Indexed Access Types完全指南 T K 语法、可选索引访问与 $PropertyType 迁移 本文是 Flow文档教程知识库OpenWorkflow并行工作流实战如何高效处理300个并发任务OpenWorkflow并行工作流实战如何高效处理300个并发任务 OpenWorkflow是一个开源TypeScript框架专为构建持久化、可恢复的工作流cp-algorithms 详解最大流 MPM 算法O(V³) 的势能式阻塞流算法cp algorithms 详解最大流 MPM 算法O V³ 的势能式阻塞流算法 本篇文章围绕 cp algorithms 仓库中的 MPM 算法文档 htt文档教程知识库上一篇LikeC4架构伦理技术伦理考量的可视化工具终极指南下一篇helium-chromium 构建工具链中的 third_party 目录与 schema 校验库以 downloads.ini 验证为例创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考