最大流算法全解析:从Ford-Fulkerson到Dinic,实战代码与原理详解

发布时间:2026/11/14 19:18:46

最大流算法全解析:从Ford-Fulkerson到Dinic,实战代码与原理详解 目录引入Ford-Fulkerson算法Edmonds-Karp 算法Dinic算法最大流最小割定理练习摘要本文系统介绍了最大流算法的核心概念与经典实现。首先通过物流配送网络的实际案例引入流网络模型阐述容量限制与流量守恒两大基本性质。随后详细解析了三种主流算法Ford-Fulkerson 算法基于深度优先搜索与残存网络构建反向边的增广路径思想Edmonds-Karp 算法通过广度优先搜索确保每次找到最短增广路径提升稳定性Dinic 算法结合 BFS 分层与 DFS 多路增广并引入当前弧优化显著提高效率。文章还从数学角度证明了流叠加引理并阐述了最大流最小割定理的工程意义。最后提供了完整的 C 语言实现代码链式前向星存储及一系列经典练习题为读者构建了从理论到实践的知识体系。引入配送网络采用 “中心仓 — 中转场 — 末端站点” 三级结构所有包裹从龙华中心分拣仓发出经城市中转场集散后配送至各区末端站点。每条运输线路受道路通行能力、货运班次限制存在单日最大运力上限。运营团队需在 618 大促前测算全网单日最大可配送包裹总量并定位运力瓶颈以制定备货与调度方案。源点 S龙华中心分拣仓一级中转节点A - 深圳北站中转场、B - 平湖分拣中心二级中转节点C - 银湖中转场、D - 西丽中转场末端汇点E - 南山配送站、F - 福田配送站、G - 罗湖配送站SA12SB10AC8AD9BC6BG7CF11CG5DE10DF4构建网络流。流必须要满足两个条件1容量限制。对于所有的结点要求2流量守恒。对于所有的结点要求从点A流出的流量不能超过x从点A流出多少点B就能接收多少。流的值Ford-Fulkerson算法为了解决这样的问题先尝试使用深度搜索算法从源点到汇点找出一条路径更新容量再找出一条路径重复这种操作直到无法找到路径。在更新容量的时候先尝试用原容量减去流量。很快我们发现了问题dfs寻找的路径一开始可能不是朝着最大流方向寻找的导致最大流求解错误。下图的流网络中第一次dfs后更新容量发现无法继续找到路径最大流为10但实际上最大流为20。有什么方法可以让dfs迷途知返呢当流发生更新时流中所有的路径段容量减去流的值同时构建反向边反向边的容量为流的值。这种存在反向边的网络可以称作残存网络。在残存网络中找从源点到汇点的路径就是找增广路径。我们可以发现流之间相互叠加最终构成了最大流。我们想通过数学的方式证明这一点。设为一个流网络源结点为汇点为设为中的一个流。设由流所诱导的残存网络设为中的一个流。其值为。下面引用《算法导论第三版》26.1流网络中的证明方式证明容量守恒又证明流量守恒定义为有边从源结点s到达的结点集合为有边通往s的结点集合。我们有并且因为不允许有反平行边我们有。现在来计算将v的范围全部扩展到结点集合V上有补充。增广路径为在残存网络中从源点 s 到汇点 t 的简单路径顶点不重复路径上所有边残量 0。如下图所示S为起点T为终点各段路径默认流量非0红色箭头组成路径为增广路径的是选B选项。选项A存在回路选项C存在残量为0的边选项D存在顶点重复。下面走一遍Ford-Fulkerson算法。s为源点t为汇点最大流为20。示例代码C语言、链式前向星#include stdio.h #include string.h #include stdbool.h #define MAX_VEX 100 // 最大顶点数 #define MAX_EDGE 1000 // 最大边数量正向反向边 #define INF 99999999 // 代表无穷大 // ---------- 链式前向星结构 ---------- int head[MAX_VEX]; // head[u]u节点第一条边下标 int to[MAX_EDGE]; // 边的终点 int next[MAX_EDGE]; // 下一条边 int cap[MAX_EDGE]; // 边残量 int edge_cnt; // 边计数器 // 添加边正向边 反向边残量初始0 void add_edge(int u, int v, int c) { to[edge_cnt] v; cap[edge_cnt] c; next[edge_cnt] head[u]; head[u] edge_cnt; to[edge_cnt] u; cap[edge_cnt] 0; next[edge_cnt] head[v]; head[v] edge_cnt; } bool visited[MAX_VEX]; // DFS 访问标记 /* DFS 寻找一条从 u 到 t 的增广路径 u: 当前顶点, t: 汇点, f: 流入 u 的流量上限 返回值: 找到的增广路径上的瓶颈流量 */ int dfs(int u, int t, int f) { if (u t) return f; visited[u] true; for (int e head[u]; e ! -1; e next[e]) { int v to[e]; if (!visited[v] cap[e] 0) { int bottleneck dfs(v, t, f cap[e] ? f : cap[e]); if (bottleneck gt; 0) { cap[e] - bottleneck; cap[e ^ 1] bottleneck; return bottleneck; } } } return 0; } /* Ford-Fulkerson 主算法 */ int ford_fulkerson(int s, int t) { int max_flow 0; int augment; // 逗号表达式先清空访问标记再找增广路找到增广路则进入循环累加 while (memset(visited, 0, sizeof(visited)), (augment dfs(s, t, INF)) 0) { max_flow augment; } return max_flow; } // ---------- 测试 ---------- int main() { memset(head, -1, sizeof(head)); edge_cnt 0; int s 0, v1 1, v2 2, v3 3, v4 4, t 5; add_edge(s, v1, 16); add_edge(s, v2, 13); add_edge(v1, v3, 12); add_edge(v2, v1, 4); add_edge(v2, v4, 14); add_edge(v3, v2, 9); add_edge(v3, t, 20); add_edge(v4, v3, 7); add_edge(v4, t, 4); int result ford_fulkerson(s, t); printf(最大流 Max Flow %d\n, result); // 输出 23 return 0; }Edmonds-Karp 算法FF算法在寻找增广路径的时候喜欢四处跑时间不可控。如果将DFS替换为BFS相对来说会稳定很多。示例代码C语言、链式前向星#include stdio.h #include string.h #include stdbool.h #define MAX_VEX 100 #define MAX_EDGE 1000 #define INF 99999999 // ---------- 链式前向星 ---------- int head[MAX_VEX]; int to[MAX_EDGE]; int next[MAX_EDGE]; int cap[MAX_EDGE]; int edge_cnt; // 添加正向边 反向边 void add_edge(int u, int v, int c) { to[edge_cnt] v; cap[edge_cnt] c; next[edge_cnt] head[u]; head[u] edge_cnt; to[edge_cnt] u; cap[edge_cnt] 0; next[edge_cnt] head[v]; head[v] edge_cnt; } // BFS记录前驱parent[v] (from节点, 边编号) int parent_node[MAX_VEX]; int parent_edge[MAX_VEX]; int n; // 顶点数 /* BFS 在残量网络中寻找一条从 s 到 t 的最短增广路径 返回值true 表示找到路径false 表示无路可走 */ bool bfs(int s, int t) { memset(parent_node, -1, sizeof(parent_node)); memset(parent_edge, -1, sizeof(parent_edge)); int queue[MAX_VEX]; int front 0, rear 0; queue[rear] s; parent_node[s] s; // 源点标记 while (front rear) { int u queue[front]; // 遍历u所有出边 for (int e head[u]; e ! -1; e next[e]) { int v to[e]; // 残量0 且未访问 if (parent_node[v] -1 cap[e] 0) { parent_node[v] u; parent_edge[v] e; // 记录到达v使用的边 if (v t) return true; queue[rear] v; } } } return false; } /* Edmonds-Karp 主算法 */ int edmonds_karp(int s, int t) { int max_flow 0; // 核心循环只要 BFS 能找到增广路径 while (bfs(s, t)) { // 1. 寻找瓶颈路径上的最小残量 int bottleneck INF; for (int v t; v ! s; v parent_node[v]) { int e parent_edge[v]; if (cap[e] bottleneck) { bottleneck cap[e]; } } // 2. 更新残量网络 for (int v t; v ! s; v parent_node[v]) { int e parent_edge[v]; cap[e] - bottleneck; // 正向边容量减少 cap[e ^ 1] bottleneck; // 反向边容量增加异或取反向边 } // 3. 累加最大流 max_flow bottleneck; } return max_flow; } // ---------- 测试 ---------- int main() { memset(head, -1, sizeof(head)); edge_cnt 0; int s 0, v1 1, v2 2, v3 3, v4 4, t 5; n 6; // 用到最大节点编号5总共6个节点0~5 // 建图 add_edge(s, v1, 16); // S -gt; v1 add_edge(s, v2, 13); // S -gt; v2 add_edge(v1, v3, 12); // v1 -gt; v3 add_edge(v2, v1, 4); // v2 -gt; v1 add_edge(v2, v4, 14); // v2 -gt; v4 add_edge(v3, v2, 9); // v3 -gt; v2 add_edge(v3, t, 20); // v3 -gt; t add_edge(v4, v3, 7); // v4 -gt; v3 add_edge(v4, t, 4); // v4 -gt; t int result edmonds_karp(s, t); printf(Edmonds-Karp 最大流结果: %d\n, result); // 标准答案 23 return 0; }Dinic算法EK算法找到汇点后立刻返回一条增广路然后重新DFS如果能多路增广效率就会提升很多。DFS的工作职责不再是找增广路径而是为残存网络分层分层后再通过DFS找增广路径。弧优化在朴素 Dinic 的 DFS 增广过程中每次访问一个节点时都会从头遍历该节点的所有邻接边。但在同一轮 BFS 分层周期内很多边已经失去了增广价值边的剩余容量已经耗尽无法再通过流量从这条边出发后续路径根本无法到达汇点。示例代码C语言、链式前向星#include stdio.h #include string.h #include stdbool.h #define MAXN 10005 // 最大顶点数 #define MAXM 200005 // 最大边数包含反向边 #define INF 0x3f3f3f3f // ---------- 链式前向星数据结构 ---------- int head[MAXN]; int to[MAXM]; int next[MAXM]; int cap[MAXM]; int edge_cnt; void add_edge(int u, int v, int c) { // 正向边 to[edge_cnt] v; cap[edge_cnt] c; next[edge_cnt] head[u]; head[u] edge_cnt; // 反向边初始容量为0用于退流 to[edge_cnt] u; cap[edge_cnt] 0; next[edge_cnt] head[v]; head[v] edge_cnt; } // ---------- Dinic 算法核心变量 ---------- int level[MAXN]; // 节点层次 int cur[MAXN]; // 当前弧优化指针 int queue[MAXN]; // BFS 手写队列 int n; // 总节点数 // BFS 构建分层图 bool bfs(int s, int t) { memset(level, -1, sizeof(int) * n); int front 0, rear 0; level[s] 0; queue[rear] s; while (front lt; rear) { int u queue[front]; for (int e head[u]; e ! -1; e next[e]) { int v to[e]; if (cap[e] gt; 0 amp;amp; level[v] -1) { level[v] level[u] 1; queue[rear] v; } } } return level[t] ! -1; } // DFS 多路增广一次调用推完所有可行增广路返回从u出发的总推送流量 int dfs(int u, int t, int flow) { if (u t || flow 0) return flow; int total_push 0; // 累计从当前节点推送出去的总流量 // 从当前弧开始遍历流量耗尽则提前退出 for (int *e_ptr amp;cur[u]; *e_ptr ! -1 amp;amp; flow gt; 0; *e_ptr next[*e_ptr]) { int v to[*e_ptr]; if (cap[*e_ptr] gt; 0 amp;amp; level[v] level[u] 1) { int bottleneck dfs(v, t, flow lt; cap[*e_ptr] ? flow : cap[*e_ptr]); if (bottleneck gt; 0) { // 更新残量网络 cap[*e_ptr] - bottleneck; cap[(*e_ptr) ^ 1] bottleneck; total_push bottleneck; flow - bottleneck; // 剩余可推送流量减少 } } } return total_push; } // Dinic 主算法 int dinic(int s, int t) { int max_flow 0; while (bfs(s, t)) { // 每轮分层后重置当前弧 for (int i 0; i lt; n; i) cur[i] head[i]; // 一次DFS求出本轮阻塞流直接累加 max_flow dfs(s, t, INF); } return max_flow; } // ---------- 测试 ---------- int main() { memset(head, -1, sizeof(head)); edge_cnt 0; int s 0, v1 1, v2 2, v3 3, v4 4, t 5; n 6; // 建图 add_edge(s, v1, 16); add_edge(s, v2, 13); add_edge(v1, v3, 12); add_edge(v2, v1, 4); add_edge(v2, v4, 14); add_edge(v3, v2, 9); add_edge(v3, t, 20); add_edge(v4, v3, 7); add_edge(v4, t, 4); int result dinic(s, t); printf(Dinic 最大流结果: %d\n, result); return 0; }最大流最小割定理对于一个有源点 s、汇点 t 的有向流网络若将顶点全集 V 划分为两个互不相交的子集 S 和 T满足源点在源侧汇点在汇侧划分完整则这个二元组就称为该网络的一个s-t 割。流网络G中任意流f的值不能超过G的任意切割的容量。在下图中最大流的值为23最小割的值也为23红箭头。在工程中找到最小割提升效率可以缩短工期。练习洛谷 P3376 【模板】网络最大流AcWing 2171. EK 求最大流洛谷 P2756 飞行员配对方案问题AcWing 2240. 餐饮Dining洛谷 P2764 最小路径覆盖问题洛谷 P1344 【模板】最小割AcWing 2234. 多源汇最大流洛谷 P2774 方格取数问题LeetCode LCP 04. 覆盖洛谷 P2057 [SHOI2007] 善意的投票
延伸阅读

更多相关文章

2026/11/8 14:27:34

最小可运行示例:用 curl 与 Python 调通心灵毒鸡汤 API 的完整笔记

从一个空请求体说起 不少内容类接口要求调用方在请求体里塞满业务字段,而心灵毒鸡汤接口是一个另类:它接收一个空的 JSON 对象即可触发随机文案返回。这种设计很适合用来练习最小可运行示例——用最少的代码、最少的参数,验证一条完整的请求链…

2026/11/12 0:04:08

抖音下载器:从创意到实践,打造你的个人媒体素材库

抖音下载器:从创意到实践,打造你的个人媒体素材库 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallbac…

2026/11/14 19:17:35

渭南哪里办理电工证?上岗必备全流程看这里

渭南哪里办理电工证?上岗必备全流程看这里 之前考的证在外省能不能转过来?这几乎是每个准备在渭南从事电工工作的人都会问的问题。尤其像我们这类在漯河水利工程从业的朋友,经常需要跨省流动,证照的转移和认可就成了摆在眼前的难题。别担心,今天我就来帮…

2026/11/14 19:17:35

电工证年审到中益教育怎么操作?网上查询流程全解析

电工证年审到中益教育怎么操作?网上查询流程全解析 证书过期了不知道怎么复审延期?很多电工从业人员都遇到过这个问题,尤其是像洛阳涧西区的应届工程类毕业生,初次步入职场时对特种作业操作证的管理流程并不熟悉,稍有不慎就可能面临证书失效,影响工作机…

2026/11/14 19:17:35

汉台电工证考试怎么报名?实操考试心里没底怕挂科

汉台电工证考试怎么报名?实操考试心里没底怕挂科 实操考试心里没底怕挂科,这是很多准备参加 汉台电工证考试 的学员最真实的感受。尤其是对于第一次报考的“小白”,面对高压电工、低压电工、焊接与热切割等特种作业项目,既想快速拿到证书,又担心自己操…

2026/11/14 19:17:35

高压电工证登高作业范围千万别踩坑:过期了怎么复审延期全知道

高压电工证登高作业范围千万别踩坑:过期了怎么复审延期全知道 证书过期了不知道怎么复审延期,这事儿千万别踩坑。特别是高压电工证登高作业范围这类证书,关系到你的作业资格和职业发展。如果操作不当,不仅影响工作,还可能面临行政处罚,甚至被吊销证书。…

2026/11/14 19:12:34

特种工皖l是哪的证官方回应:证书过期了不知道怎么复审延期?

特种工皖l是哪的证官方回应:证书过期了不知道怎么复审延期? 证书过期了不知道怎么复审延期?你不是一个人。特种作业证一旦过了有效期,再想上岗就可能面临被停工、罚款,甚至被列入行业黑名单的风险。尤其是像【特种工皖l是哪的证】这类证件,很多人只在…

2026/11/13 22:33:32

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/14 0:06:39

土木特种工考证官方报名入口怎么找 之前考的证在外省能不能转过来

土木特种工考证官方报名入口怎么找 之前考的证在外省能不能转过来 之前考的证在外省能不能转过来?这个问题我太熟悉了,作为一名干了十几年的特种作业证书办理从业者,每年都会遇到不少像你一样在外地考了证,回来却发现无法直接使用的学员。别急,今天我就…

2026/11/14 0:06:39

电工证要多久多钱保姆级教程:工地太忙根本没时间复习考试

电工证要多久多钱保姆级教程:工地太忙根本没时间复习考试 工地太忙根本没时间复习考试,你不是一个人。在信阳浉河区,很多刚进入工地的电工都面临这样的困境:工作强度大,时间碎片化,根本没有系统复习考试的机会。但电工证是上岗的硬门槛,…

2026/11/13 22:32:11

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/13 12:14:39

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑