最大流量(Maximum Flow)可以一句话理解为:
“把水管网络看成图,求从进水口到出水口最多能同时流过多少水,且每根水管都有流量上限。”
下面用 生活故事 + 手算动画 + 相关算法 + 代码 给你彻底讲透。
🚰 生活故事:自来水厂到小区
-
源点 s:自来水厂
-
汇点 t:你家小区
-
中间管道:每根水管有 最大容量
-
目标:在不爆管的前提下,最多能送多少水?
🖐️ 手算动画
-
🧩 手算小图
边 & 容量(吨/小时)
S→A 10
S→B 8
S→C 12
A→T 15
B→T 10
C→D 6
D→T 9
问题:从 S(自来水厂)到 T(你家)最多能同时送多少吨?
-
🖐️ 手算步骤(像给水管贴标签)
① 先画空图,所有边标 剩余容量
S→A 10/10
S→B 8/8
S→C 12/12
A→T 15/15
B→T 10/10
C→D 6/6
D→T 9/9
(斜杠左边是 已用流量,右边是 剩余容量)
② 找第一条增广路径(随便挑)
路: S → A → T
瓶颈 = min(10, 15) = 10
把整条路径 减 10:
S→A 10/0 (已用 10,剩余 0)
A→T 10/5 (已用 10,剩余 5)
总流量 = 10
③ 再找第二条增广路径
路: S → B → T
瓶颈 = min(8, 10) = 8
更新:
S→B 8/0
B→T 8/2
总流量 = 10 + 8 = 18
④ 再找第3条增广路径
-
路: S → C → D → T
瓶颈 = min(12,6,9) = 6更新:
S→C 6/6 C→D 6/0 D→T 6/3总流量 = 18 + 6 = 24
⑤ 检查还有路可走吗?
-
S→A 已用完
-
S→B 已用完
-
S→C 还剩 6,但 C→D 已用完,无法到达 T
再也找不到新路径 → 结束!
-
✅ 结论
| 增广路径 | 流量 |
|---|---|
| S→A→T | 10 |
| S→B→T | 8 |
| S→C→D→T | 6 |
| 最大流量 | 24 |
-
🧠 记忆口诀
“一条路一条路地找,每次只塞 最细水管 的容量,直到再也塞不进为止。”
🧮 官方算法
| 算法 | 思路 | 复杂度 | 一句话 |
|---|---|---|---|
| Edmonds-Karp (EK) | BFS 找增广路径 | O(V·E²) | 每次找最短增广路 |
| Dinic | 分层图 + 多路增广 | O(V²·E) | 现代竞赛/工程首选 |
| ISAP | Dinic 的常数优化 | ≈ Dinic | 竞赛模板 |
📐 分层(BFS 建层)
生活动作:
把每个路口按“离水厂多少站”贴楼层号:S(0) → A(1) → T(2) ↘ B(1) → T(2)
只能从低层到高层走,防止 兜圈子(成环)。
分层一次 O(V+E)。
🔍 多路增广(DFS 塞水)
生活动作:
从水厂出发,沿着“下一层”的路口,能塞多少塞多少,直到塞不动为止。实时操作:
找出 瓶颈容量(路径上最细的水管)。
把整条路径的流量 减掉瓶颈,同时在 反向边加上瓶颈(方便反悔)。
累加总流量。
下面给出 Dinic 模板,因为它 好写 + 快 + 通用。
💻 Dinic 算法详解
| 阶段 | 动作 | 路径 | 瓶颈 | 累计流量 | 剩余图简述 |
|---|---|---|---|---|---|
| 0 | 初始 | — | — | 0 | 所有边满容量 |
| 1 | 分层+BFS | S(0)→A(1)→T(2) | 10 | 10 | S→A 0/10;A→T 5/15 |
| 2 | 分层+BFS | S(0)→B(1)→T(2) | 8 | 18 | S→B 0/8;B→T 2/10 |
| 3 | 分层+BFS | S(0)→C(1)→D(2)→T(3) | 6 | 24 | S→C 6/12;C→D 0/6;D→T 3/9 |
| 4 | 无法再分层 | — | — | 24 | 结束 |
import java.util.*;
public class MaxFlowDemo {
/* ====== Edge ====== */
static class Edge {
int to, rev, cap;
Edge(int t, int r, int c) { to = t; rev = r; cap = c; }
}
/* ====== Dinic ====== */
static class Dinic {
List<Edge>[] g;
int[] level, ptr;
Dinic(int n) {
g = new ArrayList[n];
for (int i = 0; i < n; i++) g[i] = new ArrayList<>();
level = new int[n];
ptr = new int[n];
}
void addEdge(int u, int v, int cap) {
g[u].add(new Edge(v, g[v].size(), cap));
g[v].add(new Edge(u, g[u].size() - 1, 0));
}
boolean bfs(int s, int t) {
Arrays.fill(level, -1);
Queue<Integer> q = new LinkedList<>();
level[s] = 0; q.add(s);
while (!q.isEmpty()) {
int u = q.poll();
for (Edge e : g[u])
if (e.cap > 0 && level[e.to] == -1) {
level[e.to] = level[u] + 1;
q.add(e.to);
}
}
return level[t] >= 0;
}
int dfs(int u, int t, int flow) {
if (u == t) return flow;
for (; ptr[u] < g[u].size(); ptr[u]++) {
Edge e = g[u].get(ptr[u]);
if (e.cap > 0 && level[e.to] == level[u] + 1) {
int f = dfs(e.to, t, Math.min(flow, e.cap));
if (f > 0) {
e.cap -= f;
g[e.to].get(e.rev).cap += f;
return f;
}
}
}
return 0;
}
int maxFlow(int s, int t) {
int flow = 0;
while (bfs(s, t)) {
Arrays.fill(ptr, 0);
int f;
while ((f = dfs(s, t, Integer.MAX_VALUE)) > 0) flow += f;
}
return flow;
}
}
/* ====== 测试 ====== */
public static void main(String[] args) {
int n = 6; // 0:S, 1:A, 2:B, 3:C, 4:D, 5:T
Dinic mf = new Dinic(n);
/* 按题目输入建图 */
mf.addEdge(0, 1, 10); // S→A
mf.addEdge(0, 2, 8); // S→B
mf.addEdge(0, 3, 12); // S→C
mf.addEdge(1, 5, 15); // A→T
mf.addEdge(2, 5, 10); // B→T
mf.addEdge(3, 4, 6); // C→D
mf.addEdge(4, 5, 9); // D→T
System.out.println("最大流量 = " + mf.maxFlow(0, 5)); // 输出 24
}
}
✅ 记忆口诀
最大流 = 反复找还能塞水的路,直到塞满;Dinic 用“分层+多路 DFS”一次塞饱。
875






