《二分图最大匹配》基础概念

一、算法概述

  1. 定义:匈牙利算法(Hungarian Algorithm)用于求解二分图的最大匹配问题,即在一个二分图中,找到最多的边的集合,使得集合中的任意两条边都没有公共顶点。
  2. 核心思想:通过不断寻找增广路径(从非匹配点出发,交替经过非匹配边和匹配边,最终到达另一个非匹配点的路径),并对增广路径上的边进行取反操作(匹配边变为非匹配边,非匹配边变为匹配边),从而增加匹配边的数量,直到不存在增广路径为止。
  3. 应用场景
    • 任务分配问题:将多个任务分配给多个工人,每个工人只能执行一个任务,每个任务只能由一个工人执行,求最优分配方案。
    • 资源调度问题:在资源有限的情况下,将资源分配给不同的项目,使资源利用率最大化。
    • 稳定婚姻问题:在男女配对中,找到一个稳定的配对方案,使得不存在男女双方都更倾向于对方而不是当前配偶的情况。

二、算法思路

  1. 数据结构
    • 使用邻接表adj存储二分图的边信息,adj[i]表示左边点集i号顶点的所有边对应的右边顶点。
    • 数组pre记录右边顶点的匹配情况,pre[i]表示右边顶点i匹配的左边顶点编号。
    • 数组visit标记右边顶点是否在当前寻找增广路径过程中被访问过。
  2. 初始化
    • 清空所有边信息,并将pre数组初始化为 -1,表示所有右边顶点初始时都未匹配。
  3. 寻找增广路径
    • 从左边点集的每个顶点出发,尝试寻找增广路径。
    • 对于当前顶点u,遍历其所有邻接的右边顶点v,若v未被访问过,则标记v已访问。
    • 检查v是否未匹配(pre[v] == -1),若未匹配则直接将vu匹配;若已匹配,则递归尝试为v的当前匹配顶点pre[v]寻找新的匹配。
  4. 更新最大匹配数
    • 每次找到一条增广路径,最大匹配数加1,直到左边点集的所有顶点都尝试完毕,最终得到二分图的最大匹配数。

三、伪代码实现

1. 数据结构与全局变量

常量 maxn = 510  # 左边点集的最大顶点数
常量 maxm = 510  # 右边点集的最大顶点数
数组 adj[maxn][]  # 邻接表存储二分图的边,adj[i] 存储左边顶点 i 对应的右边顶点列表
数组 pre[maxm]  # pre[i] 记录右边顶点 i 匹配的左边顶点编号
数组 visit[maxm]  # visit[i] 标记右边顶点 i 是否在当前寻找增广路径过程中被访问过
变量 n  # 左边点集的实际顶点数量
变量 m  # 右边点集的实际顶点数量

2. 核心函数框架

算法:匈牙利算法求解二分图最大匹配
输入:二分图的左边顶点数 n,右边顶点数 m,图的边信息
输出:二分图的最大匹配数

函数 Hungarian_Init(n_, m_):
    n = n_  # 初始化左边点集数量
    m = m_  # 初始化右边点集数量
    对 pre 数组的每个元素赋值为 -1  # 初始化右边顶点匹配情况为未匹配
    for i 从 1 到 n:
        清空 adj[i]  # 清空左边顶点 i 的邻接边列表

函数 Hungarian_AddEdge(u, v):
    将 v 添加到 adj[u] 中  # 添加从左边顶点 u 到右边顶点 v 的边

函数 Hungarian_findMatch(u):
    for i 从 0 到 adj[u] 的长度 - 1:
        v = adj[u][i]  # 获取与左边顶点 u 邻接的右边顶点 v
        if not visit[v]:  # 若 v 未被访问过
            visit[v] = true  # 标记 v 已访问
            vpre = pre[v]  # 记录 v 当前匹配的左边顶点
            pre[v] = u  # 尝试将 v 与 u 匹配
            if vpre == -1 或者 Hungarian_findMatch(vpre):
                return true  # 找到增广路径,返回成功
            pre[v] = vpre  # 恢复 v 的原匹配
    return false  # 未找到增广路径,返回失败

函数 Hungarian_GetMaxMatch():
    cnt = 0  # 初始化最大匹配数为 0
    for i 从 1 到 n:
        对 visit 数组的每个元素赋值为 false  # 重置访问标记
        if Hungarian_findMatch(i):
            cnt = cnt + 1  # 找到增广路径,匹配数加 1
    return cnt  # 返回最大匹配数

# 主程序(示例)
输入左边顶点数 n 和右边顶点数 m
Hungarian_Init(n, m)
输入边的数量 e
for i 从 0 到 e - 1:
    输入左边顶点 u 和右边顶点 v
    Hungarian_AddEdge(u, v)
输出 Hungarian_GetMaxMatch()  # 输出二分图的最大匹配数

四、算法解释

1. 初始化过程(Hungarian_Init)

  • 接收二分图左边和右边点集的顶点数量,分别赋值给全局变量nm
  • pre数组所有元素设为 -1,表示右边顶点初始均未匹配。
  • 清空邻接表adj中每个左边顶点对应的边列表,为后续添加边做准备。

2. 添加边过程(Hungarian_AddEdge)

  • 对于输入的边(u, v),将右边顶点v添加到左边顶点u的邻接表adj[u]中,用于后续寻找增广路径时遍历与u相连的右边顶点。

3. 寻找增广路径过程(Hungarian_findMatch)

  • 从左边顶点u出发,遍历其邻接表adj[u]中的每个右边顶点v
  • v未被访问过,标记v为已访问,并记录v当前匹配的左边顶点vpre = pre[v],尝试将vu匹配(pre[v] = u)。
  • v未匹配(vpre == -1),则直接找到了一条增广路径;若v已匹配,则递归调用Hungarian_findMatch(vpre),尝试为v的当前匹配顶点vpre寻找新的匹配。若递归调用成功,则说明存在增广路径;若失败,则恢复v的原匹配(pre[v] = vpre)。
  • 若遍历完u的所有邻接顶点都未找到增广路径,则返回false

4. 计算最大匹配数过程(Hungarian_GetMaxMatch)

  • 从左边点集的第一个顶点开始,依次调用Hungarian_findMatch函数寻找增广路径。
  • 每次调用Hungarian_findMatch成功找到增广路径后,将最大匹配数cnt加1。
  • 遍历完左边点集的所有顶点后,cnt即为二分图的最大匹配数,返回该值。

5. 示例演示

  • 假设二分图左边点集有3个顶点,右边点集有3个顶点,边的连接情况为(1, 1)(1, 2)(2, 2)(3, 3)
  • 初始化后,pre数组元素均为 -1,adj邻接表为空。
  • 添加边后,adj[1] = [1, 2]adj[2] = [2]adj[3] = [3]
  • 从左边顶点1开始寻找增广路径,找到顶点1与右边顶点1匹配;继续寻找,顶点1还可与右边顶点2匹配,更新匹配;从顶点2寻找,顶点2与右边顶点2匹配;从顶点3寻找,顶点3与右边顶点3匹配。最终最大匹配数为3。

五、复杂度分析

  1. 时间复杂度
    • 最坏情况下,对于左边点集的每个顶点都要进行一次寻找增广路径的操作,每次寻找增广路径的过程中,需要遍历该顶点的所有邻接边,假设二分图中左边点集有n个顶点,右边点集有m个顶点,边的数量为e,则每次寻找增广路径的时间复杂度为 O ( e ) O(e) O(e),因此匈牙利算法的时间复杂度为 O ( n × e ) O(n \times e) O(n×e)。在稠密图中, e = O ( n × m ) e = O(n \times m) e=O(n×m),此时时间复杂度为 O ( n × n × m ) O(n \times n \times m) O(n×n×m);在稀疏图中, e = O ( n + m ) e = O(n + m) e=O(n+m),时间复杂度为 O ( n × ( n + m ) ) O(n \times (n + m)) O(n×(n+m))
  2. 空间复杂度
    • 邻接表adj存储图的边信息,空间复杂度为 O ( e ) O(e) O(e),其中e为边的数量;数组previsit的长度分别为右边点集的顶点数m,空间复杂度为 O ( m ) O(m) O(m)。因此,总的空间复杂度为 O ( e + m ) O(e + m) O(e+m),在最坏情况下, e = O ( n × m ) e = O(n \times m) e=O(n×m),空间复杂度为 O ( n × m + m ) = O ( n × m ) O(n \times m + m) = O(n \times m) O(n×m+m)=O(n×m)

  本文为作者(英雄哪里出来)在抖音的独家课程《英雄C++入门到精通》、《英雄C语言入门到精通》、《英雄Python入门到精通》三个课程的配套文字讲解,如需了解算法视频课程,请移步 作者本人 的抖音直播间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

英雄哪里出来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值