《图算法与计算机网络》系列 · 第 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
瓶颈识别:
最小割中的边就是网络的瓶颈
要增加总流量,必须增加这些边的容量
应用价值:
- 瓶颈分析:找到限制网络性能的关键边
- 网络优化:指导应该优先扩容哪些链路
- 可靠性评估:最小割的大小反映网络的鲁棒性
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 算法
- 思想:通过不断寻找增广路径来增加流量
- 关键概念 :
- 残量网络:反映剩余可用容量
- 增广路径:从源点到汇点的路径
- 反向边:允许"反悔"之前的流量分配
- 时间复杂度:O(V × E²)(Edmonds-Karp 实现)
- 空间复杂度:O(V + E)
- 应用:带宽分配、交通优化、任务分配
最大流最小割定理
- 定理内容:最大流 = 最小割
- 应用价值 :
- 证明算法正确性
- 识别网络瓶颈
- 指导网络优化
- 实际意义:找到限制系统性能的关键因素
算法实现要点
- 残量计算 :
residual = capacity - flow + reverse_flow - 反向边处理:流量增加时,正向边 +f,反向边 -f
- 路径记录:使用 parent 字典记录增广路径
- 终止条件:无法找到增广路径
实际应用注意事项
-
多源点多汇点:
- 添加超级源点和超级汇点
- 超级源点到各源点的容量根据需要设定
- 各汇点到超级汇点的容量根据需要设定
-
无向图处理:
- 将无向边转换为两条有向边
- 每条有向边的容量等于原边容量
-
节点容量:
- 如果节点也有容量限制
- 将节点拆分为入点和出点
- 入点到出点的边容量为节点容量
-
最小费用流:
- 在容量的基础上增加费用维度
- 使用 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
└─ 最大流最小割定理