二分最大匹配(匈牙利算法+HK算法)

http://acm.hdu.edu.cn/showproblem.php?pid=2063

匈牙利算法:http://blog.csdn.net/dark_scope/article/details/8880547

Hopcroft-Karp算法:http://blog.csdn.net/xu3737284/article/details/8973720

问题概述: 一场聚会上有n个男生m个女生,每个男生都可能对多名女生有好感,如果有一对男女有好感,他们就可

以被撮合在一起,否则不能,请问在这个聚会中最多能有多少对男女撮合在一起?(二分图最大匹配)

输入样例:                             对应输出:

6 3 3                                        3

1 1

1 2

1 3

2 1

2 3

3 1

最小点覆盖数:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边

独立集:图中任意两个顶点都不相连的顶点集合

交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。

增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路

→定理1:二分图最大匹配==最小点覆盖数

→定理2:顶点数-二分图最大匹配==最大独立集

→定理3:最大匹配是增广路不存在的充要条件

匈牙利算法:

解决问题:二分图最大匹配,本质DFS,复杂度V*E


#include<stdio.h>
#include<string.h>
int k, n, m, road[505][505], vis[505], link[505];
int Sech(int x);
int main(void)
{
	int i, a, b, ans;
	while(scanf("%d", &k), k!=0)
	{
		ans = 0;
		scanf("%d%d", &n, &m);
		memset(road, 0, sizeof(road));
		memset(link, 0, sizeof(link));
		for(i=1;i<=k;i++)
		{
			scanf("%d%d", &a, &b);		/*road[a][b]==1表示第a个男生对第b个女生有好感,否则没有*/
			road[a][b] = 1;
		}
		for(i=1;i<=n;i++)		/*算法思路:从第一个男生开始,尽可能让每个男生匹配成功,或是还有心仪的女生名花无主直接匹配,或是调整前面已有的匹配已达到找到自己心仪的女生*/
		{
			memset(vis, 0, sizeof(vis));	/*vis为标记数组,vis[k]==1表示第k个女生已经被查找过并试图改变过该妹子的归属问题,没有必要再次查找*/
			if(Sech(i)==1)		/*第i个男生匹配成功*/
				ans++;
		}
		printf("%d\n", ans);
	}
	return 0;
}

int Sech(int x)
{
	int i;
	for(i=1;i<=m;i++)	/*对于每个男生,先枚举每一个女生*/
	{
		if(vis[i]==0 && road[x][i]==1)		/*如果这个女生还未被查找且这个男生对这个女生有好感*/
		{
			vis[i] = 1;		/*标记*/
			if(link[i]==0 || Sech(link[i])==1)		/*如果这个女生名花无主或是可以腾出个位置*/
			{			/*这其实是一个寻找增广路的过程*/
				link[i] = x;		/*恭喜这一对男女配对成功*/
				return 1;
			}
		}
	}
	return 0;
}


Hopcroft-Karp算法:

主要思想:在每次增广的时候同时寻找几条不相交的最短增广路,形成极大增广路集,随后可以沿着这几条增广路

同时进行增广

解决问题:二分图最大匹配,本质BFS,复杂度sqrt(V)*E


#include<stdio.h>
#include<string.h>
#include<queue>
using namespace std;
int n, m, dis, val[1005][1005], lx[1005], ly[1005], dx[1005], dy[1005], vis[1005];
int HKSech();					/*lx、ly表示左集合右集合的点连接情况*/
int Sech(int u);
int main(void)
{
	int k, u, v, i, ans;
	while(scanf("%d", &k), k!=0)
	{
		scanf("%d%d", &n, &m);
		memset(val, 0, sizeof(val));
		while(k--)
		{
			scanf("%d%d", &u, &v);
			val[u][v] = 1;
		}
		ans = 0;
		memset(lx, -1, sizeof(lx));
		memset(ly, -1, sizeof(ly));
		while(HKSech())
		{
			memset(vis, 0, sizeof(vis));
			for(i=1;i<=n;i++)
			{
				if(lx[i]==-1 && Sech(i))		/*如果i点还没有连接(可能是增广路的起始),则尝试进行连接*/
					ans++;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

int HKSech()
{
	int i, x;
	dis = 100000000;
	queue<int> q;
	memset(dx, -1, sizeof(dx));			/*dx、dy用来标记当前点是否在一个交替路中*/
	memset(dy, -1, sizeof(dy));
	for(i=1;i<=n;i++)
	{
		if(lx[i]==-1)		/*初始化左集合中没有连接的点入队*/
		{
			q.push(i);
			dx[i] = 0;
		}
	}
	while(q.empty()==0)			/*广搜寻找目前最短的增广路集合*/
	{				/*在寻找增广路集的每一个阶段所寻找到的最短增广路都具有相等的长度,并且随着算法的进行最短增广路的长度是越来越长,最多只需要增广ceil(sqrt(n))次就可以得到最大匹配*/
		x = q.front();
		q.pop();
		if(dx[x]>dis)		/*已经找到,结束*/
			break;
		for(i=1;i<=m;i++)
		{								/*以下x∈左集合,i∈右集合*/
			if(val[x][i] && dy[i]==-1)		/*如果x与i有边且i点还未被搜到*/
			{
				dy[i] = dx[x]+1;		/*i点在一条交替路中,标记*/
				if(ly[i]==-1)		/*如果i点没有和任何一点连接,这条交替路为增广路!*/
					dis = dy[i];
				else
				{
					dx[ly[i]] = dy[i]+1;
					q.push(ly[i]);
				}
			}
		}
	}
	if(dis==100000000)			/*如果找不到增广路,说明已经满足最大匹配*/
		return 0;
	return 1;
}

int Sech(int x)
{
	int i;
	for(i=1;i<=m;i++)
	{
		if(val[x][i] && vis[i]==0 && dy[i]==dx[x]+1)	/*如果i在一条交替路中*/
		{
			vis[i] = 1;
			if(ly[i]!=-1 && dy[i]==dis)		/*如果i点已经被连接但是当前交替路的长度==找到的增广路的长度,说明这条路不是当前最短增广路*/
				continue;
			if(ly[i]==-1 || Sech(ly[i]))		/*如果i点未连接,或者可以腾出个位置*/
			{
				ly[i] = x;
				lx[x] = i;
				return 1;
			}
		}
	}
	return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值