匈牙利算法—介绍与基本用途

匈牙利算法应用于二分图(即可以分为两大部分,且个部分内不连接的图)匹配的问题,它的时间复杂度为O(nm)。它的基本原理是增广路

它的用途主要有三:1、单纯二分图匹配;2、最小点覆盖;3、最大独立集。下面,我将一一介绍。


一、单纯二分图匹配

例题1:

有n只公牛和m只母牛,然后每只公牛都可以和几只的母牛配对。在每只公牛只能配对一只母牛的情况下,求能为牛们配对最多多少对?

思路:公牛是二分图的一个集合,母牛也是。接着公牛逐一询问母牛,会出现两种情况。1、如果母牛未被匹配,公牛匹配它;2、如果母牛已被匹配,询问母牛的原配公牛能否换另一头母牛匹配。若行,则该公牛可以获得此母牛;反之,该公牛无法得到该母牛。如果该失配公牛问遍了所有母牛,仍然不能找到合适的配偶,则该公牛匹配失败(ans不能+1)。


代码:

#include
#include
using namespace std;

int n,m,ans;
int match[210];//母牛i的配偶是公牛match[i]
bool chw[210];//在此趟询问中,母牛i是否被询问过
bool mp[210][210];//公牛i与母牛j是否有关系

bool find_ans(int x)
{
	for(int i=1;i<=m;i++)
	{
		if(mp[x][i]==true&&chw[i]==true)
		{
			chw[i]=false;
			if(match[i]==0||find_ans(match[i])==true)
			//母牛没有配偶||匹配该母牛的公牛能否换一头母牛匹配 
			{
				match[i]=x;
				return true;
			}
		}
	}
	return false;
}

int main()
{
	while(scanf("%d %d",&n,&m)!=EOF)
	{
		memset(mp,false,sizeof(mp));
		for(int i=1;i<=n;i++)
		{
			int k,x;
			scanf("%d",&k);
			for(int j=1;j<=k;j++)
			{
				scanf("%d",&x);
				mp[i][x]=true;
			}
		}
		ans=0;
		memset(match,0,sizeof(match));
		for(int i=1;i<=n;i++)
		{
			memset(chw,true,sizeof(chw));
			if(find_ans(i)==true) ans++;
		}
		printf("%d\n",ans);
	}
	return 0;
}

解题关键:构造二分匹配图,找到可连接的线的条件(mp[ ][ ]==true?)。


二、最小点覆盖

一般考场上的题目极少是纯匈牙利算法,多数会盖上“最小的覆盖”的薄纱。

对于求最小点覆盖的题,我们要运用结论:二分图中 最小点覆盖数 = 最大匹配数 ,因此求最小点覆盖的覆盖的题转化为了求最大匹配数的题。


例题2:(poj 1325)

有两部机器A和B。A机器有n种工作模式0,1,2,3…… n-1 总共n种;B机器有m中工作模式0,1,2,3…… m-1 总共m种。有k个任务,每个任务可以在A机器的某个模式或者B机器的某个模式中完成。A和B机器开始时都默认在0模式,要选择其他模式就要重启一次。求完成k个任务至少需要重启多少次机器。

思路:构图:X集合表示A机器的模式编号,Y集合表示B机器的模式编号,如果两个模式能完成的任务相同,则给它们连边,也就是说,任务用来表示


例题3:(poj 3692)

G个女孩,B个男孩,女孩之间相互认识,男孩之间相互认识,某些男孩同女孩之间相互认识,求最大的相互认识的集合的人数。

思路:该题棘手于“女孩之间相互认识,男孩之间相互认识”,因为如果就此构图,会出现G集合和B集合内相互连边,不符合二分图的定义,因此,我们得另辟新路来构图。构图:把没有关系的两点连接,反向建图(即建补图)。


代码:

#include
#include
using namespace std;

int g,b,m,ans;
int match[210];
bool mp[210][210];//mp[i][j]==true时,表示i女生与j男生互不认识
bool chw[210];

bool find_ans(int x)
{
	for(int i=1;i<=b;i++)
		if(mp[x][i]==true&&chw[i]==true)
		{
			chw[i]=false;
			if(match[i]==0||find_ans(match[i])==true)
			{
				match[i]=x;
				return true;
			}
		}
	return false;
}

int main()
{
	int Case=0;
	while(scanf("%d%d%d",&g,&b,&m)&&g!=0&&b!=0&&m!=0)
	{
		Case++;
		memset(mp,true,sizeof(mp));//互不认识 
		for(int i=1;i<=m;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			mp[x][y]=false;//他俩认识 
		}
		
		ans=0;
		memset(match,0,sizeof(match));
		for(int i=1;i<=g;i++)
		{
			memset(chw,true,sizeof(chw));
			if(find_ans(i)==true) ans++;//统计不认识的人数 
		}
		printf("Case %d: %d\n",Case,g+b-ans);//输出 总人数-不认识人数 
	}
	return 0;
}


三、最大独立集

有关最大独立集的题,我们如果利用结论:最大独立集 点数= 总点数 - 最小点覆盖 = 总点数 - 最大匹配数  ,就可以用匈牙利算法轻松求解。若要求独立集的数量,可以通过强联通来求。

如 例题3,它在求“最大的相互认识的集合的人数”其实本质就是求最大独立集点数,所以它的答案(最大独立集点数)= 总人数(总点数)- 不认识人数(最大匹配数)。


例题4:(poj 1466)

一些女生与男生有浪漫关系,求一个集合中的最大人数,满足这个集合中两两的人不能配对。其中男生与男生间和女神与女生间本就有有浪漫关系。

思路:构图:把X集合的人复制到Y集合,如果两个人有浪漫关系,则给他们连边,最后通过 最大独立集点数=总点数-最大匹配数 得到答案,但需注意,此图中的点相当于两个X集合,所以得到的最大匹配数要除以2。


文章最后,介绍关于匈牙利算法的一种简单的优化方法:建邻接表。以节省不必要的重复判断,这样可以大大减少时间,但空间可能会有改变,码量要大些。

你可能感兴趣的:(二分图匹配)