【图论】图文详解匈牙利算法

一、一些概念

1.二分图
  • 一定不含有奇数环,可能包含长度为偶数的环, 不一定是连通图
  • 二分图是图论中的一种特殊模型。设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。简单来说,如果图中点可以被分为两组,并且使得所有边都跨越组的边界,则这就是一个二分图。准确地说:把一个图的顶点划分为两个不相交子集 ,使得每一条边都分别连接两个集合中的顶点。如果存在这样的划分,则此图为一个二分图,如下图所示的全都是二分图:
    在这里插入图片描述

关于二分图有一个重要的定理:G为二分图的充要条件是G中的每一个环的长度都是偶数 在这里就不证明了。

2.二分图的匹配
  • 二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
  • 二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

理解起来是不是很抽象?没关系,我来举一个例子:让我们假设二分图的左边全为男生,右边全为女生,线连着的男女生为情侣关系,允许出现脚踏n条船等混乱的男女关系的情况,那么:

  • 二分图的匹配:把二分图删除一些边使男女生之间的关系没有出现脚踏n条船的情况,就说删除边后得到的新图为一个匹配(允许出现单身狗的情况)
  • 二分图的最大匹配:删除部分边使得保留的情侣数量最多,我们就称这个匹配为最大匹配。

比如在上面的4张图中,图1就是图3的一个最大匹配。

二、匈牙利算法的实现步骤

在故事的最开始,作为爱神丘比特的你得到了4对男女生之间的关系图
在这里插入图片描述
真是混乱的男女关系啊…

不过没关系,我们将化为纯爱战士,来让他们混乱的男女关系恢复正常!开始干活!

1.情况一(你是我的唯一)

首先我们看向男1,发现男1很纯情的只喜欢着女2,那么就成全他们吧。确定他们两个人的情侣关系。
在这里插入图片描述

2.情况二(你们都是我的翅膀)

接下来我们看向男2,发现男2喜欢着女1和女3两个女孩子。问问男2吧,他表示:我对这两个女孩子都是真心的,选谁都行!

选谁都行啊,那我们就随便选吧,把男2和女1牵上红线。
在这里插入图片描述

3.情况三(我会把你抢过来)

搞定,然后我们再看看男3,男3表示:我也喜欢女1啊!明明是我更加喜欢她!为什么?为什么她和别人在一起了啊!我不能接受!

嗯…看来我们的男3不想放弃啊,那我们尝试和男2交涉一下。

“男2呀,你有备胎吗?”
“有啊,怎么了?”
“男3看上了你女朋友,要不你和你备胎在一起,把你女朋友让给别人吧”
“嗯…好吧,记得让他请我吃饭”(作者对男2这种渣男表示强烈谴责!)

ok,这样的话事情就圆满解决了,可喜可贺可喜可贺。
在这里插入图片描述

4.情况四(我爱的人已经有了爱人)

解决了男2和男3的问题,我们再看向男4。

男4说:我喜欢女3!我想和女3在一起!
我看了看,女3不是男2的新女友吗?额…我再去找男2看看吧。

“什么?还要我换?大哥,我没别的备胎了,我拒绝!要是我还有备胎的话还差不多。”(作者对男2这种渣男表示强烈谴责!)

我们只好回头土脸的找到男4。那个,我们交涉失败了,女3是没戏了,要不你换一个追求对象我帮你争取一下?

男4低头沉思了一下,“我觉得吧,女4其实也挺可爱的。”

ok,安排!我们看了看,发现女4还是单身呢,那就成全你们吧。
在这里插入图片描述
最后,我们得到的最大匹配就是这样
在这里插入图片描述

男女关系正常了,纯爱党一脸满足~

  • 总结:算法描述:
    如果你想找的妹子已经有了男朋友,
    你就去问问她男朋友,
    你有没有备胎,
    有备胎就把你女朋友让给我
    你没有备胎我就只好找我的备胎

多么真实而实用的算法~
时间复杂度为O(nm),但是实际操作中时间一般会小于n*m。
多好的算法啊,连时间复杂度都这么优雅~

三、匈牙利算法的代码实现

例题链接:Acwing 二分图的最大匹配
匈牙利算法板子题,是必须要掌握的题目

给定一个二分图,其中左半部包含 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≤1e5
输入样例:
2 2 4
1 1
1 2
2 1
2 2
输出样例:
2

代码实现及分析

//#pragma GCC optimize(2)
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<ctime>
#include<cstring>
#include<list>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef  pair<int, int> PII;
const int N = 1e6 + 7;
int n1, n2, m;
int h[550], ne[N], e[N], id = 1;// 链式前向星存图
bool st[N];//防止重边的情况导致死循环,防止一个男生重复询问一个女生导致死循环
int match[N];//用来存女生匹配的男生的编号

void add(int a, int b)
{
	e[id] = b, ne[id] = h[a], h[a] = id++;
}

int find(int x)  //找男生x能不能匹配到女生
{
	for (int i = h[x]; i != -1; i = ne[i])  //遍历所有的边
	{
		int j = e[i];
		if (!st[j])  //防止重边导致的死循环
		{
			st[j] = true;
			if (!match[j] || find(match[j]))  //如果这个女生还没有男朋友或者她现在的男朋友有备胎
			{
				match[j] = x;  //那就让她男朋友换备胎,然后她成为x的女朋友
				return true;  //匹配成功
			}
		}
	}
	return false;  //匹配失败
}


void solve()
{
	mem(h, -1);
	cin >> n1 >> n2 >> m;
	while (m--)
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
	}
	int res = 0;
	for (int i = 1; i <= n1; i++)
	{
		mem(st, false);  //重置
		if (find(i))  //给i找女朋友啦
			res++;
	}
	cout << res << endl;
}

int main()
{
	std::ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	solve();
	return 0;
}


作者:Avalon Demerzel,喜欢我的博客就点个赞吧,更多图论与数据结构知识点请见作者专栏《图论与数据结构》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值