神机百炼3.55-匈牙利算法

本文详细介绍了匈牙利算法在二分图最大匹配问题中的应用,包括算法原理、实现步骤及代码实现,并通过实例讲解了如何高效解决此类问题。

匈牙利算法导图

食用指南:

对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目侧重题目分析,代码实现,以及必要的代码理解误区

题目描述:

  • 给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m 条边。
    数据保证任意一条边的两个端点都不可能在同一部分中。
    请你求出二分图的最大匹配数。
    二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
    二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

    输入格式
    第一行包含三个整数 n1、 n2 和 m。
    接下来 m 行,每行包含两个整数 u 和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。

    输出格式
    输出一个整数,表示二分图的最大匹配数。

    数据范围
    1≤n1,n2≤500,
    1≤u≤n1,
    1≤v≤n2,
    1≤m≤105
    输入样例:
    2 2 4
    1 1
    1 2
    2 1
    2 2
    输出样例:
    2

  • 题目来源:https://www.acwing.com/problem/content/863/

题目分析:

  • 匈牙利算法的基本应用场景-求二分图最大匹配

  • 图论中两部分点数n1 & n2都在500范围内
    最大边数可达500 * 500 = 250000

  • 可以采用邻接表存储,共500列邻接表,每列邻接表含有500个节点。

    也可以采用邻接矩阵存储,亦为500*500大小

  • 下面来讲O(mn)解决二分图最大匹配的匈牙利算法 — 又称找对象算法

算法原理:

模板算法:

匈牙利算法:

1. 存储形式:
  • 将二分图中点划分为两部分:男生 & 女生
    男生为主动尝试匹配的一方。

  • 男生的目标女生:

    邻接表中h[x]下拉的每一个节点val[],都是男生的目标女生,最终男生和其中的一个女生构成匹配。

  • 女生的对象数组:

    fa[x] = y,表示女生x的对象是男生y

  • 对象冲突记录:

    comm[x] = 1,表示现在女生x在已有男友fa[x]的情况下,出现其他男生希望与x匹配。

2. 算法核心:
  • 最大匹配的任务就是妥善解决**对象冲突**问题

  • 对象冲突:

    当男生y希望匹配已经有对象fa[x]的女生x时,称为发生对象冲突。

  • 解决方式:

    尝试让女生x的男友fa[x],去匹配他的其余目标女生。

    若匹配成功,则y充当女生x的男友;

    若匹配失败,则y继续寻找他的下一个目标女生。

3. 复杂度分析:
  • 对于每个男生都要尝试进行匹配,耗时O(n)
  • 每个男生最坏结果就是向所有目标女生都尝试匹配,之后才知道匹配成功/失败,耗时O(m)
  • 总耗时O(mn)

写作步骤:

  • 对于每个男生y,为其匹配女生均经过两步:

1. 清空冲突,遍历目标女生集:

  • 清空上个男生进行匹配时造成的冲突数组comm[N]
  • 遍历h[y]连接的所有节点,val[]为女生序号

2. 尝试与无对象冲突的女生匹配

  • 对象冲突的标记只用于女生x的原配男生fa[x]尝试换个女生时,避免与x匹配,
  • 对象冲突标记其实不影响y这一次尝试与x匹配,毕竟尝试前一定无冲突,尝试后也不一定造成冲突。

3. 若女生x无对象:

  • 男生y直接成为女生x的对象,fa[x]=y,匹配结束。

4. 若女生x有对象:

  • 为女生x标记对象冲突,尝试让x的原配fa[x]在其目标女生中寻找非x的女生匹配。

    fa[x]另匹配成功,则y成为x的对象,y也匹配成功。

    fa[x]另匹配失败,则fa[x]还是x的对象,y去匹配其余目标女生。

代码实现:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, M=100010;	//点数500,边数可上万,静态单链表idx可上万
int h[N], val[M], ne[M];
int idx;
void add(int x, int y){
    val[idx] = y;
    ne[idx] = h[x];
    h[x] = idx++;
}
int fa[N];
bool comm[N];
bool dfs(int x){
    for(int i=h[x]; i!=-1; i=ne[i]){
        if (!comm[val[i]]){         //在没有对象冲突的女生中寻找对象(这句话主要针对发生冲突时,原配男友换女生时不再考虑原配女生)
            if (!fa[val[i]]){       //该女生没有男友,则x直接充当其男友
                fa[val[i]] = x;
                return 1;
            }else {                 //该女生有男朋友,则此时x和其原配男友构成对象冲突,尝试让原配男友换一个女生
                comm[val[i]] = 1;
                if (dfs(fa[val[i]])){
                    fa[val[i]] = x;
                    comm[val[i]] = 0;
                    return 1;
                }
            }
        }
    }
    return 0;
}
int n1, n2, m;
int main(){
    memset(h, -1, sizeof h);
    cin >>n1 >>n2 >>m;
    int a=0, b=0;
    while(m--){
        cin >>a >>b;
        add(a, b);
    }
    int res = 0;
    for(int i=1; i<=n1; i++){
        memset(comm, 0, sizeof comm);
        if (dfs(i)){
            res++;
        }
    }
    cout<<res <<endl;
    return 0;
}

代码误区:

1. 为什么每个男生匹配前要将冲突集合都清空?

  1. 在这个男生进行匹配前,所有冲突不是由他引发,与他要进行匹配无关。
  2. 原来存在过对象冲突的女生,在解决一次对象冲突之后,还可以继续和其余男生产生对象冲突。
  3. 男生必须尝试与发生过对象冲突的女生匹配,毕竟该女生的现任男友在匹配到该女生后就停止匹配了。也就是现任男友未和所有目标女生进行匹配,则不知道现在的二分图匹配数是不是MAX。

2. 为什么遍历目标女生集合时,要先检测是否存在对象冲突。

  1. 主要是为了防止原配男友切换对象时,又匹配到当前存在冲突的女生。
  2. 对于没有对象的男生尝试和一个女生匹配的过程,这个条件相当于没有限制,不影响。

3. 易错点:邻接表大小

  • 500个点去匹配外500个点,最多需要250000个节点,而不是500个节点。
  • 邻接表本质是静态单链表,所有节点同一索引于idx变量
  • 若仅开500节点,则对于规模较大的数据会超时报错:Time Limit Exceeded

本篇感想:

  • 匈牙利算法本质就4句话:
    遍历目标集合,选择无冲突对象进行匹配,对象暂未匹配则建立新匹配,对象已经匹配则尝试让对象的对象换个对象匹配。
  • 图论部分的算法基础讲解完毕,接下来是dynamic programming & 贪心。
    现在在考虑啥时候写语言博客和linux博客
  • 看完本篇博客,恭喜已登 《筑基境-后期》
    筑基境

距离登仙境不远了,加油 登仙境初期

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

starnight531

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

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

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

打赏作者

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

抵扣说明:

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

余额充值