V

算法可视化

ZHEN
分享到微博分享到 XGitHub 仓库个人主页
学习工具算法复杂度速查算法学习路径
数据结构数组链表栈队列树堆哈希表图字典树并查集LRU 缓存跳表线段树B+ 树布隆过滤器树状数组
经典排序算法冒泡排序鸡尾酒排序双调排序选择排序插入排序二分插入排序希尔排序归并排序自顶向下归并快速排序三路快排双轴快排堆排序计数排序基数排序桶排序
图算法Dijkstra 最短路Kruskal 最小生成树Prim 最小生成树Bellman-Ford 最短路拓扑排序Floyd 多源最短路强连通分量2-SAT最大流二分图匹配LCA 倍增欧拉路径
动态规划编辑距离0-1 背包完全背包最长公共子序列最长递增子序列硬币找零方案数石子合并旅行商 TSP树形 DP数位 DP换根 DP
回溯与搜索N 皇后子集生成全排列组合总和迷宫寻路岛屿数量单词搜索数独A* 寻路
字符串KMP 字符串匹配Rabin-KarpBoyer-MooreManacher后缀数组LCP / height 数组AC 自动机Z 函数
数学与数论埃氏筛线性筛欧几里得算法快速幂扩展欧几里得中国剩余定理欧拉函数米勒-拉宾FFTPollard's Rho
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

最大流(Ford-Fulkerson)

图算法 · 网络流 · 残量网络 · 最大流 = 最小割

从源到汇最多能送多少

有一张有向图,每条边有一个容量(一秒最多流过多少水)。从源 s往汇 t送水,每条边的流量不能超过容量、每个中间点流入 = 流出。问:最多能送多少?这就是最大流问题,它是任务分配、二分图匹配、图像分割等一大类问题的通用模型。

残量网络与反向边

Ford-Fulkerson 方法:反复找一条从 s 到 t 的增广路(每条边都还有剩余容量),沿路把流推满瓶颈(路径上最小的剩余容量),直到再也找不到增广路。关键在残量网络:每条用了流量的边 u→v 会生出一条容量等于「已用流量」的反向边v→u。它的作用是反悔——如果之前贪心走错了路,后面可以沿反向边把流退回、改道。正因为有反向边,任意顺序找增广路都能得到正确的最大流。

下面固定一张 4 点网络。故意让第一条增广路走「贪心陷阱」 s→a→b→t(占用了中间边 a→b)。点「下一步」逐步看:每条边标「流量/容量」,琥珀是正向增广、红色虚线是反向退流。到第 4 轮,增广路 s→b→a→t 反向经过 a→b,把第 1 轮误走的那 1 单位退回、改道。走到底,最大流 6。右侧代码随每一步同步高亮。

0/30/30/10/30/3s源abt汇

网络:源 s、汇 t,每条边标「流量/容量」(初始全 0)。反复在残量网络找增广路、推满瓶颈,直到再也找不到

1function maxFlow(n: number, edges: [number, number, number][], s: number, t: number): number {
2 const cap = Array.from({ length: n }, () => new Array(n).fill(0));
3 for (const [u, v, c] of edges) cap[u][v] += c; // 残量容量(反向边初始 0)
4 let flow = 0;
5 const dfs = (u: number, pushed: number, vis: boolean[]): number => {
6 if (u === t) return pushed;
7 vis[u] = true;
8 for (let v = 0; v < n; v++) {
9 if (!vis[v] && cap[u][v] > 0) { // 残量 > 0 才能走
10 const d = dfs(v, Math.min(pushed, cap[u][v]), vis);
11 if (d > 0) { cap[u][v] -= d; cap[v][u] += d; return d; } // 增流 + 反向边加残量
12 }
13 }
14 return 0;
15 };
16 for (;;) {
17 const pushed = dfs(s, Infinity, new Array(n).fill(false)); // 找一条增广路
18 if (pushed === 0) break;
19 flow += pushed; // 累加瓶颈
20 }
21 return flow;
22}
源 → 汇s → t
当前流量0 / 最大 6
1 / 10

最大流 = 最小割

增广路:残量网络里一条 s→t 的路径,沿路推满瓶颈。
反向边:u→v 用了 f 流量 → 生出 v→u 容量 f,允许退流改道。
停机:再无增广路即最大流;此时 s 可达集与其余点之间的边就是最小割。
Edmonds-Karp:用 BFS 找最短增广路,迭代次数被界定在 O(VE²)。

走到底,把 s 能到达的点和其余点切开,被切断的边的容量之和恰好等于最大流——这就是著名的 最大流最小割定理:送得越多,是因为「咽喉」(最小割)就那么宽。本例最小割是 s→a 与 s→b(都被打满 3/3),容量和 6 = 最大流。许多看似无关的问题(二分图最大匹配、项目选择、图像前景背景分割)都能归约成最大流/最小割来解。