1. 程式人生 > >匈牙利演算法—介紹與基本用途

匈牙利演算法—介紹與基本用途

匈牙利演算法應用於二分圖(即可以分為兩大部分,且個部分內不連線的圖)匹配的問題,它的時間複雜度為O(nm)。它的基本原理是增廣路

它的用途主要有三:1、單純二分圖匹配;2、最小點覆蓋;3、最大獨立集。下面,我將一一介紹。

一、單純二分圖匹配

例題1:

有n只公牛和m只母牛,然後每隻公牛都可以和幾隻的母牛配對。在每隻公牛隻能配對一隻母牛的情況下,求能為牛們配對最多多少對?

思路:公牛是二分圖的一個集合,母牛也是。接著公牛逐一詢問母牛,會出現兩種情況。1、如果母牛未被匹配,公牛匹配它;2、如果母牛已被匹配,詢問母牛的原配公牛能否換另一頭母牛匹配。若行,則該公牛可以獲得此母牛;反之,該公牛無法得到該母牛。如果該失配公牛問遍了所有母牛,仍然不能找到合適的配偶,則該公牛匹配失敗(ans不能+1)。

程式碼:

#include<cstdio>
#include<cstring>
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<cstdio>
#include<cstring>
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。

文章最後,介紹關於匈牙利演算法的一種簡單的優化方法:建鄰接表。以節省不必要的重複判斷,這樣可以大大減少時間,但空間可能會有改變,碼量要大些。

相關推薦

no