算法:二分图最大匹配——匈牙利算法

一想到二分图最大匹配,我就不禁想起那几个少年抢女朋友的故事,所以今天我就来说一下二分图匹配的解决方法——匈牙利算法。

例题

洛谷3386 【模板】二分图匹配

题目描述
给定一个二分图,结点个数分别为n,m,边数为e,求二分图最大匹配数。

输入格式
第一行,n, m, e。
第二至(e + 1)行,每行两个正整数u, v,表示u, v有一条连边。

输出格式

共一行,二分图最大匹配。

输入输出样例

输入

1 1 1
1 1

输出

1

说明/提示
1 <= n, m <= 1000, 1 <= e <= n * m。
因为数据有坑,可能会遇到 v>m 或者 u>n 的情况,所以请把 v>m 或者 u>n 的数据自觉过滤掉。

二分图最大匹配——匈牙利算法

先说最大匹配是什么:二分图最大匹配其实就是在已有边中选取尽量多的边,但得保证每个点最多出现一次。那么最大匹配数也就是能选取的最多边数,这里为了方便理解我们可以想象成n个男生去追m个女生,每个男生如果说对一个女生有好感(二者间有一条边),那么他们就有希望在一起,但是因为只能一夫一妻,所以我们希望能组成尽量多的情侣,这就是二分图最大匹配。举个例子,下图就是一个二分图的最大匹配,其中红边就是匹配边。

在这里插入图片描述

这里先大致说一下匈牙利算法的实现:匈牙利算法就是每次从左侧的一个点去找增广路,之后找到一条增广路径后,就将这条增广路径中所有原来的选中路径变为不选中,而原来不选中的路径改为选中。

这里具体地解释说明一下,算法主要需要维护两个数组,我就叫opp数组和vis数组了。opp[i]表示右侧点i所连接的左侧点,就是女生i当前跟哪个男生好。vis[i]表示左侧点i是否被访问过,就是男生i是否已经找到对象。

之后我们去枚举男生x,每次我们去选一个他感兴趣的女生,如果这个女生尚未有男生匹配,就让他俩在一起。或者如果这个女生有男生x0匹配了,那么我们就让男生x去跟当前女生匹配的这个男生x0抢,如果说当前女生匹配的男生x0还能去跟其他女生在一起,我们就让女生和当前男生x在一起,让原来女生匹配的男生x0去跟其他女生匹配。但如果说男生x0只能和这一个女生匹配,那么对不起了男生x,你只能去找其他你喜欢的女性。这里对于每个男生找女生的代码我就用递归来写了。

bool find(int x) // 返回男生x是否可以找到心仪的女生
{
	if (vis[x]) return false; // 如果已经枚举过了就不用再找了
	vis[x] = true; // 标记
	for (int i = 0; i < (int) g[x].size(); i++) { // 枚举男生感兴趣的女生
		int y = g[x][i]; // 获取女生y
		if (opp[y] == 0 /*女生未被任何人匹配*/ || find(opp[y]) /*当前女生匹配的男生还能找到其他女生*/) {
    		opp[y] = x; // 使女生选择男生x
    		return true; // 返回男生x找到了心仪的女生
		}
	}
	return false; // 返回男生x是个单身狗
}

这里要务必记住每次从一个男生开始找时,要将vis数组清零。还有此题有个坑,即输入的x可能大于n,输入的y也可能大于m,你省略这些不合理的数据就行了。

最后算一下算法的时间复杂度:先得枚举男生n次,之后每次还要调用find函数,其中find最多跑m次,所以总时间复杂度为O(n * m)。

代码

# include <cstdio>
# include <algorithm>
# include <cmath>
# include <cstring>
# include <vector>

using namespace std;

const int N_MAX = 1000, E_MAX = 1000000;

int n, m, e;
vector <int> g[N_MAX + 10];

int opp[N_MAX + 10];
bool vis[N_MAX + 10];

void addEdge(int x, int y)
{
	g[x].push_back(y);
}

bool find(int x)
{
	if (vis[x]) return false;
	vis[x] = true;
	for (int i = 0; i < (int) g[x].size(); i++) {
		int y = g[x][i];
		if (opp[y] == 0 || find(opp[y])) {
			opp[y] = x;
			return true;
		}
	}
	return false;
}

int hungary()
{
	memset(opp, 0, sizeof(opp));
	int ans = 0;
	for (int i = 1; i <= n; i++) {
		memset(vis, 0, sizeof(vis));
		ans += find(i);
	}
	return ans;
}

int main()
{
	scanf("%d%d%d", &n, &m, &e);
	for (int i = 1; i <= e; i++) {
		int x, y;
		scanf("%d%d", &x, &y);
		if (x > n || y > m) continue;
		addEdge(x, y);
	}
	printf("%d\n", hungary());
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值