二分图匹配算法:匈牙利算法详解
匈牙利算法是求解二分图最大匹配 的经典贪心算法,由匈牙利数学家 Edmonds 于 1965 年提出。核心思想是:通过不断寻找增广路径,逐步扩大匹配规模,直到不存在增广路径为止,此时的匹配即为最大匹配。
二分图的定义是:图的顶点可分为两个互不相交的集合 U 和 V,且每条边的两个端点分别属于 U 和 V(无内部边)。最大匹配是指二分图中边数最多的匹配(任意两条边无公共顶点)。
资料:https://pan.quark.cn/s/43d906ddfa1b、https://pan.quark.cn/s/90ad8fba8347、https://pan.quark.cn/s/d9d72152d3cf
一、核心概念
| 概念 | 定义 |
|---|---|
| 匹配 | 一组没有公共顶点的边的集合,记为 M。 |
| 匹配点 | 被匹配边覆盖的顶点;未匹配点:未被覆盖的顶点。 |
| 增广路径 | 从一个未匹配点出发,交替经过非匹配边、匹配边,最终到达另一个未匹配点的路径。增广路径的长度必为奇数,且非匹配边数比匹配边数多 1。 |
| 匹配更新 | 对增广路径上的边取反(匹配边变非匹配边,非匹配边变匹配边),可使匹配数 +1。 |
二、算法核心原理
- 初始化:匹配集合
M为空,为集合V中的顶点维护一个匹配对象数组match_to(match_to[v] = u表示v与u匹配,初始值为-1表示未匹配)。 - 增广路径查找:遍历集合
U中的每个顶点u,尝试为u寻找增广路径(深度优先或广度优先搜索)。 - 匹配扩展:若找到增广路径,更新匹配集合,匹配数加 1;否则跳过该顶点。
- 终止条件:遍历完
U中所有顶点,无新的增广路径,此时的匹配即为最大匹配。
三、算法步骤(DFS 实现)
以集合 U 为起点,V 为终点的二分图为例:
- 初始化
match_to数组为-1,记录V中顶点的匹配对象; - 对每个
u ∈ U:- 初始化访问标记数组
visited(避免重复访问V中的顶点); - 调用 DFS 函数,尝试为
u寻找增广路径;
- 初始化访问标记数组
- DFS 函数逻辑(输入为
u):- 遍历
u的所有邻接顶点v; - 若
v未被访问:- 标记
v为已访问; - 若
v未匹配,或v的匹配对象match_to[v]能找到新的增广路径,则更新match_to[v] = u,返回True(找到增广路径);
- 标记
- 遍历结束未找到,返回
False;
- 遍历
- 统计
match_to数组中非-1的元素个数,即为最大匹配数。
四、代码实现(Python,DFS 版)
python
def hungarian_dfs(graph, u_size, v_size):
"""
匈牙利算法(DFS版)求解二分图最大匹配
:param graph: 邻接表,graph[u] 表示 U中顶点u的邻接V顶点列表
:param u_size: U集合的顶点数(顶点编号 0~u_size-1)
:param v_size: V集合的顶点数(顶点编号 0~v_size-1)
:return: 最大匹配数, 匹配结果(match_to[v] = u)
"""
match_to = [-1] * v_size # V中顶点的匹配对象,初始未匹配
result = 0 # 最大匹配数
def dfs(u, visited):
"""尝试为u寻找增广路径"""
for v in graph[u]:
if not visited[v]:
visited[v] = True
# 若v未匹配,或v的匹配对象能找到新路径
if match_to[v] == -1 or dfs(match_to[v], visited):
match_to[v] = u
return True
return False
# 遍历U中每个顶点,尝试匹配
for u in range(u_size):
visited = [False] * v_size
if dfs(u, visited):
result += 1
return result, match_to
# 测试示例
if __name__ == "__main__":
# 二分图:U={0,1,2}, V={0,1,2,3}
# 邻接表:graph[u] = [v1, v2, ...]
graph = [
[0, 1], # U0 连接 V0, V1
[1, 2], # U1 连接 V1, V2
[2, 3] # U2 连接 V2, V3
]
u_size = 3
v_size = 4
max_match, match_result = hungarian_dfs(graph, u_size, v_size)
print(f"最大匹配数: {max_match}")
print(f"V→U 匹配结果: {match_result}")
# 输出:
# 最大匹配数: 3
# V→U 匹配结果: [0, 1, 2, -1]
五、BFS 版实现(Hopcroft-Karp 算法基础)
DFS 版适合小规模二分图,大规模图可使用 BFS 优化(Hopcroft-Karp 算法的基础),减少重复搜索:
python
from collections import deque
def hungarian_bfs(graph, u_size, v_size):
"""匈牙利算法(BFS版)求解二分图最大匹配"""
match_to = [-1] * v_size # V→U
dist = [0] * u_size # 分层距离,用于BFS
result = 0
def bfs():
"""构建分层图,判断是否存在增广路径"""
queue = deque()
for u in range(u_size):
if match_to[u] == -1: # U中未匹配点入队
dist[u] = 0
queue.append(u)
else:
dist[u] = float('inf')
dist_null = float('inf')
while queue:
u = queue.popleft()
if dist[u] < dist_null:
for v in graph[u]:
if match_to[v] == -1:
dist_null = dist[u] + 1
elif dist[match_to[v]] == float('inf'):
dist[match_to[v]] = dist[u] + 1
queue.append(match_to[v])
return dist_null != float('inf')
def dfs(u):
"""基于分层图的DFS增广"""
for v in graph[u]:
if match_to[v] == -1 or (dist[match_to[v]] == dist[u] + 1 and dfs(match_to[v])):
match_to[v] = u
return True
dist[u] = float('inf')
return False
# 多轮BFS+DFS,批量增广
while bfs():
for u in range(u_size):
if match_to[u] == -1:
if dfs(u):
result += 1
return result, match_to
# 测试BFS版
if __name__ == "__main__":
graph = [[0,1],[1,2],[2,3]]
u_size = 3
v_size = 4
max_match, match_result = hungarian_bfs(graph, u_size, v_size)
print(f"BFS版最大匹配数: {max_match}")
print(f"BFS版匹配结果: {match_result}")
六、算法分析
| 实现方式 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| DFS 版 | O ( V E ) O(VE) O(VE) | O ( V ) O(V) O(V) | 小规模二分图(顶点数 < 1000) |
| BFS 版 | O ( V E ) O(\sqrt{V}E) O(V E) | O ( V ) O(V) O(V) | 大规模二分图(顶点数 > 1000) |
- V V V:二分图总顶点数, E E E:边数;
- BFS 版是 Hopcroft-Karp 算法的简化版,通过分层批量增广减少 DFS 次数,效率更高。
七、典型应用场景
- 任务分配问题:员工(U)与任务(V)的匹配,每个员工只能做部分任务,求最多能完成的任务数。
- 二分图最大独立集 :根据 Konig 定理,二分图的最大独立集 = 总顶点数 - 最大匹配数。
- 点覆盖问题:二分图的最小点覆盖 = 最大匹配数(Konig 定理)。
- 婚恋匹配、资源分配等需要双向选择的场景。
八、关键注意事项
- 顶点编号映射 :若 U 和 V 的顶点编号有重叠,需先将其映射为两个不相交的区间(如 U:0n-1,V:0m-1)。
- 无向二分图处理:二分图的边是无向的,但算法中只需存储 U→V 的方向即可。
- 增广路径的核心:增广路径的本质是"调整匹配",每次找到都能让匹配数 +1,这是贪心策略的关键。
九、匈牙利算法 vs Hopcroft-Karp 算法
Hopcroft-Karp 算法是匈牙利算法的优化版,通过多轮 BFS 分层 + 批量 DFS 增广 ,时间复杂度优化到 O ( V E ) O(\sqrt{V}E) O(V E),是目前二分图最大匹配的最优算法之一,适合处理大规模二分图。