Part 4.8:网络流——从水管到算法的流量优化之旅

引言:管道中的流量

想象一下,你是一家自来水公司的工程师,负责设计一个城市的供水网络。水从水库(源点)出发,经过各种管道(边),最终流向居民区(汇点)。每条管道都有一个容量限制,表示单位时间内它能输送的最大水量。你的任务是:在不超过任何管道容量的前提下,从水库向居民区输送尽可能多的水。

这就是网络流 (Network Flow) 问题的经典场景。它是图论中一个非常重要且应用广泛的领域,为我们提供了一套完整的理论和算法,来解决各种流量优化问题。

核心概念

流网络 (Flow Network)

一个流网络是一个有向图 G = (V, E),其中:

  • 每条边 (u, v) 都有一个非负的容量 (Capacity) c(u, v)
  • 有一个特殊的顶点 s,称为**源点 (Source)**,它只有出边,没有入边。
  • 有一个特殊的顶点 t,称为**汇点 (Sink)**,它只有入边,没有出边。

流 (Flow)

一个是对每条边 (u, v) 赋予一个非负值 f(u, v),表示从 uv 的实际流量。流必须满足以下两个性质:

  1. **容量限制 (Capacity Constraint)**:对于所有边 (u, v)f(u, v) ≤ c(u, v)
  2. **流守恒 (Flow Conservation)**:对于除了源点和汇点之外的所有顶点 u,流入 u 的总流量等于流出 u 的总流量。

最大流 (Maximum Flow)

最大流问题是指:在一个流网络中,找到一个从源点 s 到汇点 t 的流,使得流的总量(从 s 流出的总量,或流入 t 的总量)最大。

残余网络 (Residual Network)

给定一个流网络和一个流 f,我们可以定义一个**残余网络 G_f**。残余网络中的边表示我们还能在原网络中增加或减少多少流量。

对于原网络中的每一条边 (u, v)

  • 如果 f(u, v) < c(u, v),说明我们还可以增加流量,在残余网络中添加一条从 uv 的边,容量为 c(u, v) - f(u, v)剩余容量)。
  • 如果 f(u, v) > 0,说明我们可以”撤销”一些流量,在残余网络中添加一条从 vu反向边,容量为 f(u, v)

增广路 (Augmenting Path)

在残余网络中,一条从源点 s 到汇点 t 的路径,称为增广路。如果我们能在残余网络中找到一条增广路,就意味着我们可以在原网络中增加流量。

增广路上的瓶颈容量(即路径上所有边的最小容量)就是我们能增加的流量。

Ford-Fulkerson 方法:不断寻找增广路

Ford-Fulkerson 方法是求解最大流问题的一种通用框架。它的核心思想非常简单:

只要残余网络中存在从源点到汇点的增广路,就沿着这条路径增加流量,直到再也找不到增广路为止。

算法步骤

  1. 初始化所有边的流量 f(u, v) = 0
  2. 构建残余网络 G_f
  3. 在残余网络中寻找一条从 st 的增广路。
  4. 如果找到了增广路:
    a. 计算这条路径上的瓶颈容量 bottleneck
    b. 沿着这条路径,对于每条边 (u, v)
    i. 增加 f(u, v) 的流量:f(u, v) += bottleneck
    ii. 减少反向边 f(v, u) 的流量:f(v, u) -= bottleneck
    c. 更新残余网络,回到步骤 3。
  5. 如果找不到增广路,算法结束,当前流就是最大流。

寻找增广路的方法

Ford-Fulkerson 方法本身并没有指定如何寻找增广路。不同的寻找方法会导致不同的算法和性能:

  • DFS:可能导致算法性能不稳定,甚至在某些情况下无法终止(如果边的容量是无理数)。
  • BFS:保证找到的增广路是边数最少的路径,这就是 Edmonds-Karp 算法

Edmonds-Karp 算法:BFS 优化的 Ford-Fulkerson

Edmonds-Karp 算法是 Ford-Fulkerson 方法的一个具体实现,它使用 BFS 来寻找增广路。

优势

  • 时间复杂度有保证O(V * E²),其中 V 是顶点数,E 是边数。
  • 稳定性好:不会出现 DFS 可能导致的性能问题。

Python 实现

from collections import deque, defaultdict

def bfs_find_path(graph, source, sink, parent):
    """使用 BFS 在残余网络中寻找增广路"""
    visited = {source}
    queue = deque([source])
    
    while queue:
        u = queue.popleft()
        
        for v in graph[u]:
            if v not in visited and graph[u][v] > 0:
                visited.add(v)
                queue.append(v)
                parent[v] = u
                if v == sink:
                    return True
    return False

def edmonds_karp(graph, source, sink):
    """
    Edmonds-Karp 算法求解最大流
    graph: 邻接矩阵表示的容量图 {u: {v: capacity}}
    source: 源点
    sink: 汇点
    返回: 最大流值
    """
    # 深拷贝图,作为残余网络
    residual_graph = defaultdict(lambda: defaultdict(int))
    for u in graph:
        for v in graph[u]:
            residual_graph[u][v] = graph[u][v]
    
    parent = {}
    max_flow = 0
    
    # 不断寻找增广路
    while bfs_find_path(residual_graph, source, sink, parent):
        # 找到瓶颈容量
        path_flow = float('inf')
        s = sink
        while s != source:
            path_flow = min(path_flow, residual_graph[parent[s]][s])
            s = parent[s]
        
        # 更新残余网络
        v = sink
        while v != source:
            u = parent[v]
            residual_graph[u][v] -= path_flow
            residual_graph[v][u] += path_flow
            v = parent[v]
        
        max_flow += path_flow
        parent = {}
    
    return max_flow

# --- 案例测试 ---
# 构建流网络
graph = defaultdict(lambda: defaultdict(int))
graph['S']['A'] = 10
graph['S']['B'] = 5
graph['A']['B'] = 15
graph['A']['T'] = 10
graph['B']['T'] = 10

max_flow_value = edmonds_karp(graph, 'S', 'T')
print(f"最大流: {max_flow_value}")

# 输出:
# 最大流: 15

最大流最小割定理

网络流理论中最重要的定理之一是**最大流最小割定理 (Max-Flow Min-Cut Theorem)**。

割 (Cut)

一个是将图的顶点集 V 分成两个不相交的子集 ST,使得源点 sS 中,汇点 tT 中。

割的容量是所有从 ST 的边的容量之和。

定理内容

在一个流网络中,从源点到汇点的最大流的值等于将源点和汇点分离的最小割的容量

这个定理不仅在理论上非常优美,在实际应用中也非常有用。例如,在网络可靠性分析中,最小割对应的边集就是网络中最脆弱的部分。

复杂度分析

算法时间复杂度空间复杂度
Ford-Fulkerson (DFS)O(E * max_flow)O(V + E)
Edmonds-Karp (BFS)O(V * E²)O(V + E)

经典应用

  1. 二分图最大匹配:可以转化为网络流问题。将二分图的两部分分别连接到源点和汇点,所有边的容量设为 1,最大流即为最大匹配。
  2. 多源多汇问题:添加一个超级源点和超级汇点,将多源多汇问题转化为单源单汇问题。
  3. 项目选择问题:在有收益和成本的项目中,选择一个子集使得净收益最大。
  4. 图像分割:在计算机视觉中,使用最小割来分割图像。

图论系列总结

恭喜你!你已经完成了整个图论算法系列的学习。让我们回顾一下这个系列涵盖的核心内容:

Part 4.1:图的表示与遍历

  • 邻接矩阵 vs 邻接表
  • BFS(广度优先搜索):层序遍历,求无权图最短路径
  • DFS(深度优先搜索):递归遍历,路径查找与环检测

Part 4.2:最小生成树 (MST)

  • Kruskal 算法:对边贪心,使用并查集
  • Prim 算法:对点贪心,使用优先队列

Part 4.3:单源最短路径

  • Dijkstra 算法:贪心,适用于非负权图
  • SPFA 算法:队列优化的 Bellman-Ford,可处理负权边

Part 4.4:多源最短路径与负权环

  • Bellman-Ford 算法:动态规划,可检测负权环
  • Floyd-Warshall 算法:动态规划,求所有点对最短路径

Part 4.5:拓扑排序

  • Kahn 算法:基于入度的 BFS
  • DFS 算法:基于完成时间的逆序

Part 4.6:强连通分量 (SCC)

  • Tarjan 算法:基于 DFS,一次遍历找出所有 SCC

Part 4.7:二分图匹配

  • 匈牙利算法:寻找增广路,求解最大匹配

Part 4.8:网络流

  • Ford-Fulkerson 方法:不断寻找增广路
  • Edmonds-Karp 算法:BFS 优化,性能稳定
  • 最大流最小割定理:理论与应用的完美结合

总结

网络流是图论中的一个高级主题,它为我们提供了一套强大的工具来解决各种流量优化问题。从最基础的图遍历,到最小生成树、最短路径,再到拓扑排序、强连通分量、二分图匹配,最后到网络流,我们完成了一次完整的图论算法之旅。

本文核心要点

流网络:有向图,每条边有容量,有源点和汇点。
最大流:从源点到汇点的最大流量。
残余网络:表示还能增加或减少多少流量的网络。
Ford-Fulkerson 方法:不断寻找增广路,增加流量。
Edmonds-Karp 算法:使用 BFS 寻找增广路,时间复杂度 O(VE²)
最大流最小割定理:最大流的值等于最小割的容量。

图论是算法领域的一座高峰,掌握了这些核心算法,你就拥有了解决各种复杂连接性问题的能力。继续探索,继续实践,你将在算法的世界中走得更远!


Part 4.8:网络流——从水管到算法的流量优化之旅
https://hjjjkh.github.io/posts/59ecaa5b
作者
李国强
发布于
2025年11月29日
许可协议