二分图匹配算法:匈牙利算法

二分图匹配算法:匈牙利算法详解

匈牙利算法是求解二分图最大匹配 的经典贪心算法,由匈牙利数学家 Edmonds 于 1965 年提出。核心思想是:通过不断寻找增广路径,逐步扩大匹配规模,直到不存在增广路径为止,此时的匹配即为最大匹配。

二分图的定义是:图的顶点可分为两个互不相交的集合 UV,且每条边的两个端点分别属于 UV(无内部边)。最大匹配是指二分图中边数最多的匹配(任意两条边无公共顶点)。

资料:https://pan.quark.cn/s/43d906ddfa1bhttps://pan.quark.cn/s/90ad8fba8347https://pan.quark.cn/s/d9d72152d3cf

一、核心概念
概念 定义
匹配 一组没有公共顶点的边的集合,记为 M
匹配点 被匹配边覆盖的顶点;未匹配点:未被覆盖的顶点。
增广路径 从一个未匹配点出发,交替经过非匹配边、匹配边,最终到达另一个未匹配点的路径。增广路径的长度必为奇数,且非匹配边数比匹配边数多 1。
匹配更新 对增广路径上的边取反(匹配边变非匹配边,非匹配边变匹配边),可使匹配数 +1。
二、算法核心原理
  1. 初始化:匹配集合 M 为空,为集合 V 中的顶点维护一个匹配对象数组 match_tomatch_to[v] = u 表示 vu 匹配,初始值为 -1 表示未匹配)。
  2. 增广路径查找:遍历集合 U 中的每个顶点 u,尝试为 u 寻找增广路径(深度优先或广度优先搜索)。
  3. 匹配扩展:若找到增广路径,更新匹配集合,匹配数加 1;否则跳过该顶点。
  4. 终止条件:遍历完 U 中所有顶点,无新的增广路径,此时的匹配即为最大匹配。
三、算法步骤(DFS 实现)

以集合 U 为起点,V 为终点的二分图为例:

  1. 初始化 match_to 数组为 -1,记录 V 中顶点的匹配对象;
  2. 对每个 u ∈ U
    • 初始化访问标记数组 visited(避免重复访问 V 中的顶点);
    • 调用 DFS 函数,尝试为 u 寻找增广路径;
  3. DFS 函数逻辑(输入为 u):
    • 遍历 u 的所有邻接顶点 v
    • v 未被访问:
      1. 标记 v 为已访问;
      2. v 未匹配,或 v 的匹配对象 match_to[v] 能找到新的增广路径,则更新 match_to[v] = u,返回 True(找到增广路径);
    • 遍历结束未找到,返回 False
  4. 统计 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 次数,效率更高。
七、典型应用场景
  1. 任务分配问题:员工(U)与任务(V)的匹配,每个员工只能做部分任务,求最多能完成的任务数。
  2. 二分图最大独立集 :根据 Konig 定理,二分图的最大独立集 = 总顶点数 - 最大匹配数
  3. 点覆盖问题:二分图的最小点覆盖 = 最大匹配数(Konig 定理)。
  4. 婚恋匹配、资源分配等需要双向选择的场景。
八、关键注意事项
  1. 顶点编号映射 :若 U 和 V 的顶点编号有重叠,需先将其映射为两个不相交的区间(如 U:0n-1,V:0m-1)。
  2. 无向二分图处理:二分图的边是无向的,但算法中只需存储 U→V 的方向即可。
  3. 增广路径的核心:增广路径的本质是"调整匹配",每次找到都能让匹配数 +1,这是贪心策略的关键。
九、匈牙利算法 vs Hopcroft-Karp 算法

Hopcroft-Karp 算法是匈牙利算法的优化版,通过多轮 BFS 分层 + 批量 DFS 增广 ,时间复杂度优化到 O ( V E ) O(\sqrt{V}E) O(V E),是目前二分图最大匹配的最优算法之一,适合处理大规模二分图。

相关推荐
IAR Systems1 天前
在IAR Embedded Workbench for Renesas RH850中实现ROPI
linux·运维·算法
一个不知名程序员www1 天前
算法学习入门--- set与map(C++)
c++·算法
鸿途优学-UU教育1 天前
法考命题趋势解读:为何越来越重视“实战能力”?
算法·法律·uu教育·法考机构
Rui_Freely1 天前
Vins-Fusion之 相机—IMU在线标定(两帧间旋转估计)(十)
人工智能·算法·计算机视觉
mit6.8241 天前
二分猜答案
算法
_OP_CHEN1 天前
【算法基础篇】(四十二)数论之欧拉函数深度精讲:从互质到数论应用
c++·算法·蓝桥杯·数论·欧拉函数·算法竞赛·acm/icpc
Eloudy1 天前
模板函数动态库与头文件设计示例
算法·cuda
星云数灵1 天前
大模型高级工程师考试练习题4
人工智能·算法·机器学习·大模型·大模型考试题库·阿里云aca·阿里云acp大模型考试题库
千金裘换酒1 天前
Leetcode 二叉树中序遍历 前序遍历 后序遍历(递归)
算法·leetcode·职场和发展