流量优化之道:Ford-Fulkerson 最大流算法

《图算法与计算机网络》系列 · 第 6 篇

本文将深入讲解网络流问题的经典算法:Ford-Fulkerson。从带宽分配到交通优化,理解如何在容量限制下最大化网络流量,揭示最大流最小割定理的深刻内涵。


系列文章导航

篇目 标题
第 1 篇 计算机网络与图算法:从理论到实践
第 2 篇 从 Dijkstra 到 A*:贪心策略与启发式搜索
第 3 篇 Bellman-Ford、SPFA 与 Floyd-Warshall:动态规划的力量
第 4 篇 构建最优网络:Prim 与 Kruskal 算法
第 5 篇 探索网络结构:BFS、DFS 与拓扑排序
第 6 篇 流量优化之道:Ford-Fulkerson 最大流算法

文章目录

    • 系列文章导航
    • 引言:如何最大化网络流量?
    • [1. 网络流问题概述](#1. 网络流问题概述)
      • [1.1 什么是网络流?](#1.1 什么是网络流?)
      • [1.2 网络中的实际应用](#1.2 网络中的实际应用)
      • [1.3 关键概念](#1.3 关键概念)
        • [残量网络(Residual Network)](#残量网络(Residual Network))
        • [增广路径(Augmenting Path)](#增广路径(Augmenting Path))
        • 割(Cut)
    • [2. Ford-Fulkerson 算法详解](#2. Ford-Fulkerson 算法详解)
      • [2.1 核心思想:增广路径](#2.1 核心思想:增广路径)
      • [2.2 算法原理图解](#2.2 算法原理图解)
      • [2.3 数据结构设计](#2.3 数据结构设计)
      • [2.4 完整代码实现](#2.4 完整代码实现)
      • [2.5 时间复杂度分析](#2.5 时间复杂度分析)
      • [2.6 最大流最小割定理](#2.6 最大流最小割定理)
    • [3. 网络应用:带宽分配与流量工程](#3. 网络应用:带宽分配与流量工程)
    • [4. 算法对比与扩展](#4. 算法对比与扩展)
      • [4.1 Ford-Fulkerson 的变体](#4.1 Ford-Fulkerson 的变体)
      • [4.2 与其他图算法的对比](#4.2 与其他图算法的对比)
      • [4.3 选择决策树](#4.3 选择决策树)
    • [5. 本章总结](#5. 本章总结)
    • 系列总结

引言:如何最大化网络流量?

实际问题引入

想象你是一个大型数据中心的管理者:

复制代码
场景 1:带宽分配
- 你的数据中心有多个入口和出口
- 服务器之间的链路有带宽限制
- 如何最大化从入口到出口的总流量?
- 哪些链路是瓶颈?

场景 2:交通优化
- 城市道路网络有车道限制
- 高峰期如何最大化车流量?
- 应该拓宽哪些道路?

场景 3:管道输油
- 输油管道网络有容量限制
- 如何最大化从油田到炼油厂的输油量?
- 如何识别关键管道?

解决方案 :这些都是经典的最大流问题


1. 网络流问题概述

1.1 什么是网络流?

网络流(Network Flow):在有向图中,从源点到汇点的流量传输模型。

复制代码
形式化定义:
给定一个有向图 G = (V, E),其中:
- 源点 s ∈ V:流量的起点
- 汇点 t ∈ V:流量的终点
- 每条边 (u,v) 有容量 c(u,v) ≥ 0

流量 f(u,v) 满足以下约束:
1. 容量限制:f(u,v) ≤ c(u,v)
   (流量不能超过容量)

2. 流量守恒:对于中间节点 v(v ≠ s, t)
   流入 v 的总流量 = 流出 v 的总流量
   Σ f(u,v) = Σ f(v,w)
   (中间节点不存储流量)

3. 对称性:f(u,v) = -f(v,u)
   (反向流量为负)

直观理解

复制代码
示例网络:
      源点 s
        /  |  \
     10/   |8  \12
      /    |    \
     A     B     C
     \    / \    /
     5\  /7  \6 /10
       \/     \/
        D     E
         \   /
        15\ /
       汇点 t

问题:从 s 到 t 最多能传输多少流量?

1.2 网络中的实际应用

场景 节点 容量 目标
网络带宽 路由器 链路 带宽 最大化吞吐量
交通网络 路口 道路 车道数 最大化车流量
管道系统 泵站 管道 管径 最大化输油量
供应链 仓库 运输路线 运力 最大化配送量
任务分配 工人/任务 分配关系 工作能力 最大化完成量
二分图匹配 左/右节点 匹配边 1 最大化匹配数

1.3 关键概念

残量网络(Residual Network)
复制代码
定义:
对于原图 G 和当前流量 f,残量网络 Gf 包含:
- 相同的节点
- 残量边:r(u,v) = c(u,v) - f(u,v)
  (剩余可用容量)

重要特性:
- 残量网络中可能出现反向边
- 反向边表示"退还"流量的可能性
- 通过反向边可以"反悔"之前的流量分配

示例:
原边:u → v,容量 10,当前流量 3
残量网络:
- 正向边:u → v,残量 7(还能增加 7)
- 反向边:v → u,残量 3(可以退还 3)
增广路径(Augmenting Path)
复制代码
定义:
在残量网络中,从源点 s 到汇点 t 的一条路径。

性质:
- 路径上的所有边都有剩余容量
- 沿增广路径可以增加流量
- 增加的流量 = 路径上最小的残量

增广过程:
1. 在残量网络中找到 s-t 路径
2. 计算路径的最小残量(瓶颈容量)
3. 沿路径增加流量
4. 更新残量网络
5. 重复直到没有增广路径
割(Cut)
复制代码
s-t 割的定义:
将节点集 V 划分为两个集合 S 和 T
- s ∈ S(源点侧)
- t ∈ T(汇点侧)

割的容量:
c(S,T) = Σ c(u,v),其中 u ∈ S, v ∈ T
(所有从 S 到 T 的边的容量之和)

割的流量:
f(S,T) = Σ f(u,v),其中 u ∈ S, v ∈ T
(所有从 S 到 T 的边的流量之和)

重要性质:
对于任意 s-t 割:
- 网络流量 = 割的流量
- 网络流量 ≤ 割的容量

2. Ford-Fulkerson 算法详解

2.1 核心思想:增广路径

基本思想

复制代码
1. 初始化:所有边的流量为 0
2. 寻找增广路径:在残量网络中找 s-t 路径
3. 增广:沿路径增加流量
4. 更新:更新残量网络
5. 重复:直到没有增广路径

核心洞察:
- 每次增广都增加总流量
- 当无法增广时,达到最大流
- 最大流最小割定理保证正确性

为什么能找到最大流?

复制代码
最大流最小割定理:
最大流的值 = 最小割的容量

证明思路:
1. 对于任意流 f 和任意割 (S,T):
   流量 |f| ≤ 割的容量 c(S,T)
   
2. 当算法终止时(无增广路径):
   - 定义 S = 从 s 可达的节点集
   - 定义 T = 其他节点
   - (S,T) 是一个 s-t 割
   
3. 对于这个割:
   - 所有从 S 到 T 的边都已饱和(f = c)
   - 所有从 T 到 S 的边流量为 0
   
4. 因此:
   流量 |f| = 割 (S,T) 的容量
   
5. 由上界可知,这是最大流

2.2 算法原理图解

让我们通过一个完整实例来理解:

复制代码
网络拓扑(4 个节点):

         s
      /     \
   10/       \10
    /         \
   A           B
    \         /
    5\       /15
      \     /
       \   /
        \ /
         t

执行过程(详细):

初始状态:
- 所有边流量为 0
- 残量网络 = 原图

第 1 次增广:
- 找到路径:s → A → t
- 路径残量:min(10, 5) = 5
- 增加流量 5
- 更新残量:
  * s→A: 10-5=5
  * A→t: 5-5=0(饱和)
  * 添加反向边:A→s (5), t→A (5)
- 当前总流量:5

第 2 次增广:
- 找到路径:s → B → t
- 路径残量:min(10, 15) = 10
- 增加流量 10
- 更新残量:
  * s→B: 10-10=0(饱和)
  * B→t: 15-10=5
  * 添加反向边:B→s (10), t→B (10)
- 当前总流量:5+10=15

第 3 次增广:
- 找到路径:s → A → B → t
- 路径残量:min(5, 5, 5) = 5
  (s→A 残量 5,A→B 残量 5,B→t 残量 5)
- 增加流量 5
- 更新残量:
  * s→A: 5-5=0(饱和)
  * A→B: 5-5=0(饱和)
  * B→t: 5-5=0(饱和)
  * 添加反向边:A→s (5), B→A (5), t→B (5)
- 当前总流量:15+5=20

第 4 次尝试:
- 在残量网络中搜索 s-t 路径
- s→A: 残量 0,不可用
- s→B: 残量 0,不可用
- 没有其他路径
- 算法终止!

最终结果:
最大流 = 20
最小割 = {(s,A), (s,B)},容量 = 10+10 = 20

2.3 数据结构设计

Ford-Fulkerson 算法需要以下数据结构:

python 复制代码
# 1. 图的表示(邻接矩阵或邻接表)
#    需要存储容量和流量
capacity = {
    's': {'A': 10, 'B': 10},
    'A': {'t': 5, 'B': 5},
    'B': {'t': 15},
    't': {}
}

# 2. 流量字典
flow = {
    's': {'A': 0, 'B': 0},
    'A': {'t': 0, 'B': 0},
    'B': {'t': 0},
    't': {}
}

# 3. 残量网络(动态计算)
#    residual[u][v] = capacity[u][v] - flow[u][v] + flow[v][u]

# 4. 父节点字典(用于记录增广路径)
parent = {}

# 5. 源点和汇点
s, t = 's', 't'

2.4 完整代码实现

python 复制代码
"""
Ford-Fulkerson 算法 - 最大流
时间复杂度:O(E × f*) - f* 为最大流值
空间复杂度:O(V + E)
"""

from collections import defaultdict, deque
from typing import Dict, Tuple, Optional


class FordFulkerson:
    """
    Ford-Fulkerson 最大流算法实现
    
    使用 BFS 寻找增广路径(即 Edmonds-Karp 算法)
    时间复杂度:O(V × E²)
    """
    
    def __init__(self, nodes):
        """
        初始化
        
        Args:
            nodes: 节点集合
        """
        self.nodes = nodes
        self.capacity = defaultdict(lambda: defaultdict(int))
        self.flow = defaultdict(lambda: defaultdict(int))
        
    def add_edge(self, u, v, capacity):
        """
        添加边
        
        Args:
            u: 起点
            v: 终点
            capacity: 容量
        """
        self.capacity[u][v] = capacity
        # 初始流量为 0
        self.flow[u][v] = 0
        self.flow[v][u] = 0
        
    def bfs(self, s, t, parent):
        """
        使用 BFS 寻找增广路径
        
        Args:
            s: 源点
            t: 汇点
            parent: 用于记录路径的字典
            
        Returns:
            是否存在增广路径
        """
        visited = {node: False for node in self.nodes}
        queue = deque([s])
        visited[s] = True
        
        while queue:
            u = queue.popleft()
            
            # 遍历所有邻居(包括正向和反向边)
            for v in self.nodes:
                # 计算残量
                residual = self.capacity[u][v] - self.flow[u][v] + self.flow[v][u]
                
                if not visited[v] and residual > 0:
                    visited[v] = True
                    parent[v] = u
                    
                    if v == t:
                        return True
                    
                    queue.append(v)
        
        return False
    
    def max_flow(self, s, t):
        """
        计算从 s 到 t 的最大流
        
        Args:
            s: 源点
            t: 汇点
            
        Returns:
            最大流值
        """
        max_flow_value = 0
        parent = {}
        
        # 不断寻找增广路径
        while self.bfs(s, t, parent):
            # 1. 找到路径的最小残量
            path_flow = float('inf')
            v = t
            
            while v != s:
                u = parent[v]
                residual = self.capacity[u][v] - self.flow[u][v] + self.flow[v][u]
                path_flow = min(path_flow, residual)
                v = u
            
            # 2. 更新流量
            v = t
            while v != s:
                u = parent[v]
                self.flow[u][v] += path_flow
                self.flow[v][u] -= path_flow  # 反向边
                v = u
            
            # 3. 累加总流量
            max_flow_value += path_flow
            
            # 4. 重置 parent,继续下一轮
            parent = {}
        
        return max_flow_value
    
    def get_flow_dict(self):
        """获取流量字典"""
        return dict(self.flow)
    
    def get_min_cut(self, s):
        """
        获取最小割
        
        Args:
            s: 源点
            
        Returns:
            (S, T) 两个节点集合
        """
        # BFS 从源点可达的节点
        visited = {node: False for node in self.nodes}
        queue = deque([s])
        visited[s] = True
        
        while queue:
            u = queue.popleft()
            
            for v in self.nodes:
                residual = self.capacity[u][v] - self.flow[u][v] + self.flow[v][u]
                if not visited[v] and residual > 0:
                    visited[v] = True
                    queue.append(v)
        
        S = {node for node in self.nodes if visited[node]}
        T = self.nodes - S
        
        return S, T


# 使用示例
if __name__ == "__main__":
    # 定义节点
    nodes = {'s', 'A', 'B', 't'}
    
    # 创建算法实例
    ff = FordFulkerson(nodes)
    
    # 添加边
    ff.add_edge('s', 'A', 10)
    ff.add_edge('s', 'B', 10)
    ff.add_edge('A', 'B', 5)
    ff.add_edge('A', 't', 5)
    ff.add_edge('B', 't', 15)
    
    # 计算最大流
    max_flow_value = ff.max_flow('s', 't')
    
    # 输出结果
    print(f"最大流:{max_flow_value}")
    
    # 输出各边流量
    print("\n各边流量:")
    flow_dict = ff.get_flow_dict()
    for u in flow_dict:
        for v in flow_dict[u]:
            if flow_dict[u][v] > 0:
                print(f"  {u}→{v}: {flow_dict[u][v]}")
    
    # 输出最小割
    S, T = ff.get_min_cut('s')
    print(f"\n最小割:")
    print(f"  S = {S}")
    print(f"  T = {T}")

运行结果

复制代码
最大流:20

各边流量:
  s→A: 10
  s→B: 10
  A→B: 5
  A→t: 5
  B→t: 15

最小割:
  S = {'s', 'A', 'B'}
  T = {'t'}

2.5 时间复杂度分析

复制代码
Ford-Fulkerson 算法(使用 BFS 寻找增广路径):

时间复杂度:O(V × E²)
- 每次 BFS:O(E)
- 最多增广次数:O(V × E)
  (每次增广至少饱和一条边)
- 总时间复杂度:O(V × E²)

特殊情况:
- 如果容量都是整数,增广次数 ≤ 最大流值 f*
- 时间复杂度:O(E × f*)
- 当 f* 很大时,效率较低

Edmonds-Karp 算法(Ford-Fulkerson 的 BFS 实现):
- 时间复杂度:O(V × E²)
- 与容量无关,更稳定

空间复杂度:O(V + E)
- 存储容量矩阵:O(V + E)
- 存储流量矩阵:O(V + E)
- BFS 队列:O(V)

对比不同实现:
| 实现方式           | 时间复杂度   | 空间复杂度 | 适用场景       |
| ------------------ | ------------ | ---------- | -------------- |
| DFS 找增广路径     | O(E × f*)    | O(V + E)   | 小容量图       |
| BFS(Edmonds-Karp)| O(V × E²)    | O(V + E)   | 通用,推荐     |
| Dinic 算法         | O(V² × E)    | O(V + E)   | 稠密图,更快   |
| 预流推进           | O(V² × √E)   | O(V + E)   | 理论最优       |

工程建议:
- 一般情况下使用 Edmonds-Karp(BFS 版本)
- 对于大规模图,考虑使用 Dinic 算法
- 如果容量范围小,DFS 版本也足够

2.6 最大流最小割定理

定理内容

复制代码
最大流最小割定理(Max-Flow Min-Cut Theorem):

在任何网络中,从源点 s 到汇点 t 的最大流量
等于分离 s 和 t 的最小割的容量。

数学表达:
max{|f|} = min{c(S,T)}

其中:
- |f| 是流 f 的值
- (S,T) 是 s-t 割
- c(S,T) 是割的容量

直观理解

复制代码
示例网络:
        s
      / | \
    10/  |  \8
    /    |   \
   A     B    C
    \    |   /
    5\   |5 /6
      \  |  /
       \ | /
         t

最小割分析:
割 1:{(s,A), (s,B), (s,C)},容量 = 10+8+8 = 26
割 2:{(A,t), (B,t), (C,t)},容量 = 5+5+6 = 16 ← 最小割
割 3:{(s,A), (B,t), (C,t)},容量 = 10+5+6 = 21

最大流 = 最小割容量 = 16

瓶颈识别:
最小割中的边就是网络的瓶颈
要增加总流量,必须增加这些边的容量

应用价值

  1. 瓶颈分析:找到限制网络性能的关键边
  2. 网络优化:指导应该优先扩容哪些链路
  3. 可靠性评估:最小割的大小反映网络的鲁棒性

3. 网络应用:带宽分配与流量工程

3.1 带宽分配问题

实际场景
复制代码
场景:数据中心网络优化

网络结构:
- 多个入口网关(接收外部请求)
- 多个出口网关(发送响应)
- 内部多层交换机
- 链路有带宽限制

问题:
- 如何最大化从入口到出口的总吞吐量?
- 哪些链路是瓶颈?
- 如何分配不同业务的带宽?

解决方案:
1. 构建网络流模型
   - 节点:网关、交换机
   - 边:物理链路
   - 容量:链路带宽

2. 运行 Ford-Fulkerson 算法
   - 计算最大吞吐量
   - 识别饱和链路(瓶颈)

3. 优化建议
   - 扩容瓶颈链路
   - 调整路由策略
   - 负载均衡
实际案例
python 复制代码
def optimize_datacenter_bandwidth():
    """
    数据中心带宽优化示例
    """
    # 定义网络拓扑
    nodes = {'gateway1', 'gateway2', 'switch1', 'switch2', 
             'server1', 'server2', 'exit1', 'exit2'}
    
    ff = FordFulkerson(nodes)
    
    # 添加链路(带宽单位:Gbps)
    # 入口层
    ff.add_edge('gateway1', 'switch1', 100)
    ff.add_edge('gateway1', 'switch2', 100)
    ff.add_edge('gateway2', 'switch1', 100)
    ff.add_edge('gateway2', 'switch2', 100)
    
    # 汇聚层
    ff.add_edge('switch1', 'server1', 40)
    ff.add_edge('switch1', 'server2', 40)
    ff.add_edge('switch2', 'server1', 40)
    ff.add_edge('switch2', 'server2', 40)
    
    # 出口层
    ff.add_edge('server1', 'exit1', 100)
    ff.add_edge('server1', 'exit2', 100)
    ff.add_edge('server2', 'exit1', 100)
    ff.add_edge('server2', 'exit2', 100)
    
    # 创建超级源点和超级汇点
    nodes.add('super_source')
    nodes.add('super_sink')
    
    ff.add_edge('super_source', 'gateway1', 200)
    ff.add_edge('super_source', 'gateway2', 200)
    ff.add_edge('exit1', 'super_sink', 200)
    ff.add_edge('exit2', 'super_sink', 200)
    
    # 计算最大流
    max_bw = ff.max_flow('super_source', 'super_sink')
    
    print(f"数据中心最大吞吐量:{max_bw} Gbps")
    
    # 识别瓶颈
    S, T = ff.get_min_cut('super_source')
    print(f"\n瓶颈链路(最小割):")
    for u in S:
        for v in ff.capacity[u]:
            if v in T and ff.capacity[u][v] > 0:
                print(f"  {u} → {v}: {ff.capacity[u][v]} Gbps")

3.2 二分图匹配

问题转换
复制代码
二分图匹配问题:
- 有两组节点:左组 L,右组 R
- 只能在 L 和 R 之间连边
- 找到最大数量的匹配(边不相交)

转换为最大流问题:
1. 添加超级源点 s,连接所有 L 中节点
   容量 = 1(每个左节点只能匹配一次)

2. 添加超级汇点 t,所有 R 中节点连接 t
   容量 = 1(每个右节点只能匹配一次)

3. 原图的边容量设为 1

4. 计算 s 到 t 的最大流
   最大流值 = 最大匹配数
应用实例
python 复制代码
def job_assignment():
    """
    工作分配问题
    
    有 n 个工人和 m 个工作
    每个工人只能做某些特定工作
    如何最大化分配的工作数量?
    """
    # 工人和技能
    workers = {'Alice', 'Bob', 'Charlie', 'David'}
    jobs = {'J1', 'J2', 'J3', 'J4'}
    
    # 工人能做的工作
    skills = {
        'Alice': ['J1', 'J2'],
        'Bob': ['J2', 'J3'],
        'Charlie': ['J1', 'J3', 'J4'],
        'David': ['J4']
    }
    
    # 构建流网络
    nodes = workers | jobs | {'s', 't'}
    ff = FordFulkerson(nodes)
    
    # s 到工人(容量 1)
    for worker in workers:
        ff.add_edge('s', worker, 1)
    
    # 工人到工作(容量 1)
    for worker, job_list in skills.items():
        for job in job_list:
            ff.add_edge(worker, job, 1)
    
    # 工作到 t(容量 1)
    for job in jobs:
        ff.add_edge(job, 't', 1)
    
    # 计算最大匹配
    max_matches = ff.max_flow('s', 't')
    
    print(f"最大工作分配数:{max_matches}")
    
    # 输出具体分配方案
    print("\n分配方案:")
    flow_dict = ff.get_flow_dict()
    for worker in workers:
        for job in jobs:
            if flow_dict[worker][job] > 0:
                print(f"  {worker} → {job}")

运行结果

复制代码
最大工作分配数:4

分配方案:
  Alice → J2
  Bob → J3
  Charlie → J1
  David → J4

3.3 交通流量优化

复制代码
城巿交通网络:
- 路口:节点
- 道路:边
- 车道数:容量

优化目标:
- 最大化从住宅区到商业区的车流量
- 识别拥堵路段
- 指导道路扩建

解决方案:
1. 构建交通网络模型
2. 计算最大流
3. 分析最小割(瓶颈道路)
4. 制定优化方案:
   - 拓宽瓶颈道路
   - 增加替代路线
   - 实施交通管制

4. 算法对比与扩展

4.1 Ford-Fulkerson 的变体

算法 增广路径寻找 时间复杂度 特点
Ford-Fulkerson DFS O(E × f*) 简单,但依赖容量
Edmonds-Karp BFS O(V × E²) 稳定,推荐使用
Dinic 分层图 + DFS O(V² × E) 更快,适合稠密图
预流推进 局部操作 O(V² × √E) 理论最优,实现复杂

4.2 与其他图算法的对比

算法类型 代表算法 优化目标 约束条件
最短路径 Dijkstra 最小化距离 无容量限制
最小生成树 Prim/Kruskal 最小化总成本 连接所有节点
最大流 Ford-Fulkerson 最大化流量 容量限制
最小费用流 Successive SP 最小化成本 流量需求 + 容量

4.3 选择决策树

复制代码
需要优化网络性能?
│
├─ 目标是最小化距离/延迟?
│  ├─ 是 → 使用 Dijkstra/Bellman-Ford(第 2、3 篇)
│  └─ 否 → 继续判断
│
├─ 目标是最小化建设成本?
│  ├─ 是 → 使用 Prim/Kruskal(第 4 篇)
│  └─ 否 → 继续判断
│
├─ 目标是最大化流量/吞吐量?
│  ├─ 是 → 使用 Ford-Fulkerson(本篇)
│  └─ 否 → 继续判断
│
├─ 需要遍历/探索网络?
│  ├─ 是 → 使用 BFS/DFS(第 5 篇)
│  └─ 否 → 其他问题类型
│
└─ 有容量限制的最大化问题?
   └─ 是 → 使用 Ford-Fulkerson(本篇)

5. 本章总结

核心要点

Ford-Fulkerson 算法
  1. 思想:通过不断寻找增广路径来增加流量
  2. 关键概念
    • 残量网络:反映剩余可用容量
    • 增广路径:从源点到汇点的路径
    • 反向边:允许"反悔"之前的流量分配
  3. 时间复杂度:O(V × E²)(Edmonds-Karp 实现)
  4. 空间复杂度:O(V + E)
  5. 应用:带宽分配、交通优化、任务分配
最大流最小割定理
  1. 定理内容:最大流 = 最小割
  2. 应用价值
    • 证明算法正确性
    • 识别网络瓶颈
    • 指导网络优化
  3. 实际意义:找到限制系统性能的关键因素
算法实现要点
  1. 残量计算residual = capacity - flow + reverse_flow
  2. 反向边处理:流量增加时,正向边 +f,反向边 -f
  3. 路径记录:使用 parent 字典记录增广路径
  4. 终止条件:无法找到增广路径

实际应用注意事项

  1. 多源点多汇点

    • 添加超级源点和超级汇点
    • 超级源点到各源点的容量根据需要设定
    • 各汇点到超级汇点的容量根据需要设定
  2. 无向图处理

    • 将无向边转换为两条有向边
    • 每条有向边的容量等于原边容量
  3. 节点容量

    • 如果节点也有容量限制
    • 将节点拆分为入点和出点
    • 入点到出点的边容量为节点容量
  4. 最小费用流

    • 在容量的基础上增加费用维度
    • 使用 Successive Shortest Path 算法
    • 每次选择费用最小的增广路径

系列总结

至此,《图算法与计算机网络》系列已全部完成!

系列回顾

篇目 主题 核心算法 应用场景
第 1 篇 基础概念 图的表示 网络建模
第 2 篇 最短路径(上) Dijkstra, A* 路由选择
第 3 篇 最短路径(下) Bellman-Ford, SPFA, Floyd 负权边处理
第 4 篇 最小生成树 Prim, Kruskal 网络设计
第 5 篇 图遍历 BFS, DFS, 拓扑排序 网络发现
第 6 篇 最大流 Ford-Fulkerson 流量优化

知识体系

复制代码
图算法知识树:
│
├─ 最短路径问题
│  ├─ 单源最短路径
│  │  ├─ 无负权边:Dijkstra, A*
│  │  └─ 有负权边:Bellman-Ford, SPFA
│  └─ 全源最短路径:Floyd-Warshall
│
├─ 最小生成树问题
│  ├─ 从点开始:Prim
│  └─ 从边开始:Kruskal
│
├─ 图遍历问题
│  ├─ 广度优先:BFS
│  ├─ 深度优先:DFS
│  └─ 拓扑排序:Kahn, DFS
│
└─ 网络流问题
   ├─ 最大流:Ford-Fulkerson, Edmonds-Karp, Dinic
   ├─ 最小费用流:Successive Shortest Path
   └─ 最大流最小割定理

相关推荐
垫脚摸太阳2 小时前
第 36 场 蓝桥·算法挑战赛·百校联赛---赛后复盘
数据结构·c++·算法
Aaswk2 小时前
刷题笔记(回溯算法)
数据结构·c++·笔记·算法·leetcode·深度优先·剪枝
NAGNIP2 小时前
一文搞懂CNN经典架构-ResNet!
算法·面试
计算机安禾2 小时前
【数据结构与算法】第14篇:队列(一):循环队列(顺序存储
c语言·开发语言·数据结构·c++·算法·visual studio
Frostnova丶2 小时前
(11)LeetCode 239. 滑动窗口最大值
数据结构·算法·leetcode
GoCoding3 小时前
YOLO-Master 与 YOLO26 开始
算法
VALENIAN瓦伦尼安教学设备3 小时前
设备对中不良的危害
数据库·嵌入式硬件·算法
不熬夜的熬润之3 小时前
APCE-平均峰值相关能量
人工智能·算法·计算机视觉
yzx9910133 小时前
实时数据流处理实战:从滑动窗口算法到Docker部署
算法·docker·容器