二分图最大匹配——匈牙利算法(附板子)

二分图定义:

将图的点分成两个集合,且每个集合内部不能有边相连,图上每条边的两端都分别来自两个集合,总之,就是每条边的两端不会来自同一个集合。这样的图称之为二分图。

如何判断二分图?

  • 首先,判断该图点集能否被分成两个独立的点集。
  • 图G为二分图的充要条件是G中的每一个环的长度都是偶数,即不存在奇数环。

二分图最大匹配:

  • 给定一个二分图G,在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配.
  • 选择这样的边数最大的子集称为图的最大匹配问题(maximal matching problem)

匈牙利算法:

  • 匈牙利算法主要解决图的最大匹配数和最小覆盖数的问题。
  • 二分图的最大匹配数等于最小覆盖数!

算法过程图:

将二分图转换为如下图:相当于左边一栏都是男孩子,右边一栏都是女孩子,现在安排两个人结婚,当然同性人不能结婚,问最多能安排多少组婚礼。

现在从B1开始选,他选择了G1,G1的原配是B1。

 

轮到B2了, 他也想选G1但是G1已经名花有主了,她的原配B1只能考虑成全B2而选择了G3,G1的原配变成了B2,G3的原配是B1

轮到B3选择了,他选G1,但是G1原配是B2,B2如果不选G1的话,那么只能去选G3,但是G3原配是B1呀,B1没有可供选择的备胎了,所以B1果断拒绝成全他人,这样的话B3也没有可供选择的备胎了,所以B3还是单身狗。QAQ

轮到B4做选择了,他选的G4并没有原配,那么就恭喜B4,他俩牵手成功!所以最大匹配就是3

以上就是匈牙利算法全过程!!

附上例题以及板子代码:

hdu 2063  过山车(板子题)

#include <bits/stdc++.h>
#define ll long long
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std;
const int N = 505;
vector<int> g[N]; //如果允许出现重边的话,那么可以直接用邻接矩阵g[u][v]做标记较好,否则可以动态数组
int pre[N], uN, vN;
bool used[N]; //标记是否配对
bool dfs(int u)
{
    for (auto v : g[u]) // v代表女主
    {
        if (!used[v]) // 女主没有被选中
        {
            used[v] = true; //假设女主v和u搭配
            if (pre[v] == -1 || dfs(pre[v])) //女主之前没有配偶或者女主配偶可以成全他俩
            {
                pre[v] = u;  //标记原配
                return true; //如果女主没有原配或者女主的原配有符合条件的备胎,那么他俩可以组合
            }
        }
    }
    return false; //如果他没有可供选择的备胎了,那么他就变成单身狗了,QAQ, 所以不能成全之前递归过来的夫妇
}
int solve()
{
    int ans = 0;
    mem(pre, -1); //原配初始值为-1
    for (int u = 1; u <= uN; u++)
    {
        mem(used, false);
        if (dfs(u)) //如果找到原配了,那么组合加1
            ans++;
    }
    return ans;
}
int main()
{
    int k;
    while (~scanf("%d",&k) && k)
    {
        scanf("%d%d",&uN, &vN);                //两个集合的大小
        for (int i = 1; i <= uN; i++) //动态数组清空
            g[i].clear();
        for (int i = 1; i <= k; i++)
        { //动态数组建边
            int u, v;
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
        }
        cout << solve() << endl;
    }
    system("pause");
    return 0;
}

洛谷 P3386 【模板】二分图最大匹配

#include <bits/stdc++.h>
#define ll long long
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std;
const int N = 505;
int pre[N], g[N][N], vN, uN;
bool used[N];
bool dfs(int u)
{
    for (int v = 1; v <= vN; v++)
    {
        if (g[u][v] && !used[v])
        {
            used[v] = true;
            if (pre[v] == -1 || dfs(pre[v]))
            {
                pre[v] = u;
                return true;
            }
        }
    }
    return false;
}
int solve()
{
    int ans = 0;
    mem(pre, -1);
    for (int u = 1; u <= uN; u++)
    {
        mem(used, false);
        if (dfs(u))
            ans++;
    }
    return ans;
}
int main()
{
    int k;
    scanf("%d%d%d",&uN,&vN,&k);
    while (k--)
    {
        int u, v;
        scanf("%d%d",&u,&v);
        g[u][v] = 1;
    }
    cout << solve() << endl;
    system("pause");
    return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值