算法知识图谱总览 / 网络流族 / 最大流(Ford-Fulkerson / Edmonds-Karp)

最大流(Ford-Fulkerson / Edmonds-Karp)

P1intermediatestandardG-B-5

What · 是什么

Ford-Fulkerson 增广路思想:残量网络 + 反向边退流;EK 用 BFS 找最短增广路 O(VE²)。

最大流问题:给定有源汇的网络 G=(V,E)(每条边有容量 c),找流 f 使从源 s 流出的总流量最大,且满足容量限制(0≤f≤c)与除源汇外各点流守恒。Ford-Fulkerson 增广法是一类算法的总称:反复在残量网络中找增广路(s 到 t 的可行路径),沿路推流;残量网络包含正向剩余容量 c-f 与反向边 f(用于退流/反悔)。Edmonds-Karp 是 FF 的 BFS 实现,保证每次找最短增广路,复杂度 O(VE²)。

Why · 为什么

直觉 · Intuition

把网络想象成水管:容量是管径,流量是水流。贪心沿一条从 s 到 t 的路径灌水会「堵死」更优的路径,所以引入反向边:水流可以被退回重走。残量网络中正向边 = 还能灌多少,反向边 = 能退回多少。只要残量网络里还有 s→t 的路径就能继续增广,找不到路径时流量即最大(增广终止定理)。

原理 · Principle

① 建图:每条边加一条反向边(cap 0),用 i^1 快速互访;② 循环:BFS/DFS 在残量网络中找 s→t 增广路,记瓶颈 Δ=min 剩余容量,路径每条边 cap-=Δ,反向边 cap+=Δ;③ 直到残量网络无 s→t 路。EK 用 BFS 保证增广路最短,增广轮数 O(VE)(最短路非递减引理),每轮 O(E),总 O(VE²)。正确性由最大流最小割定理保证:增广停止时,s 可达点集 S 构成的最小割容量等于流量。

历史 · History

Ford-Fulkerson 法由 Lester Ford 和 Delbert Fulkerson 于 1956 年提出;Edmonds 和 Karp 于 1972 年给出 BFS 实现并证明多项式复杂度 O(VE²)。Dinic(Yefim Dinitz,1970)进一步用分层图 + 阻塞流做到 O(V²E),实际性能远优于理论上界。

How · 实现

伪代码

edges = []  # [to, cap] 成对存储, i^1 互为反向
add_edge(u,v,c): edges[u]+=(v,c); edges[v]+=(u,0)
while BFS 能找到 s→t 增广路:
    Δ = min(沿路剩余容量)
    沿路正向 cap-=Δ, 反向 cap+=Δ
    flow += Δ
return flow

Python 实现

# 最大流模板见 dinic 节点(含完整实现)。此处给出 Edmonds-Karp 参考实现。
from collections import deque


def edmonds_karp(n, adj, cap, s, t):
    """EK 最大流. adj: 邻接表[[(v, edge_idx)]], cap 用 dict 或 list 均可.
    这里用简单的 dict 版方便理解:cap[(u,v)] 为剩余容量.
    复杂度 O(V E^2)."""
    cap = {k: v for k, v in cap.items()}
    flow = 0
    INF = 10 ** 18
    while True:
        pre = [-1] * n
        pre[s] = s
        q = deque([s])
        while q and pre[t] == -1:
            u = q.popleft()
            for v in adj[u]:
                if pre[v] == -1 and cap.get((u, v), 0) > 0:
                    pre[v] = u
                    q.append(v)
        if pre[t] == -1:
            break
        # 找瓶颈
        add = INF
        v = t
        while v != s:
            u = pre[v]
            add = min(add, cap[(u, v)])
            v = u
        # 增广
        v = t
        while v != s:
            u = pre[v]
            cap[(u, v)] -= add
            cap[(v, u)] = cap.get((v, u), 0) + add
            v = u
        flow += add
    return flow


if __name__ == "__main__":
    # 0->1(3) 0->2(3) 1->2(2) 1->3(3) 2->3(3), s=0,t=3, 最大流=6
    n = 4
    adj = [[1, 2], [0, 2, 3], [0, 1, 3], [1, 2]]
    cap = {(0, 1): 3, (0, 2): 3, (1, 2): 2, (1, 3): 3, (2, 3): 3}
    print("EK flow =", edmonds_karp(n, adj, cap, 0, 3))
PyPy 提示EK 的 dict 版便于理解但常数大;竞赛实现用「邻接表 + 边数组成对存储 + i^1 反向」更优。EK 只适用于小图(VE² 限制),n,m ≥ 2000 时建议直接上 Dinic。
时间复杂度O(VE²)(EK);FF 一般 O(E·|f|)(可能指数级,需整数容量且不保证终止)
空间复杂度O(V+E)
注意 · 局限FF 若用 DFS 且容量为实数,可能不终止(甚至无限);EK 的 BFS 保证多项式。Python 下 VE² 在图稍大时即超时,实际竞赛中最大流几乎都用 Dinic(见 dinic 节点)。无向边需拆成两条有向边。

易错点

例题

OJ题号难度考点链接
LuoguP33763【模板】网络最大流:EK/Dinic 模板题https://www.luogu.com.cn/problem/P3376
LuoguP27403[USACO4.2] 草地排水 Drainage Ditches:经典最大流模板https://www.luogu.com.cn/problem/P2740
CSESDownload Speed3CSES 最大流模板,Dinic 可过https://cses.fi/problemset/task/1694
步骤迷你图(节点 = 算法步骤 · 边 = 流程 · 绿色 = 起始步骤)
上游 · 本知识点依赖(前置/变体/组合/共现)
下游 · 依赖本知识点

信源

✓ = 已核验 · △ = 未核验