二分圖匹配

20516林子鈞

名詞定義

  • 匹配(Matching): 一個無向圖G = (V, E)G的匹配MM一個無向圖G = (V, E)G 中的一個邊集,M裡面任兩條邊都沒有共用點。
  • 匹配邊: 被選在目前匹配裡的邊。
  • 匹配點: 某個匹配邊的端點。
  • 最大匹配(Maximum Matching): 所有匹配中配對邊數最多者。
  • 完美匹配(Perfect Matching): 所有點都是匹配點的匹配
  • 點覆蓋 (Vertex cover): 圖上的一個點集使得所有邊都和該集合的點相鄰。 
  • 點獨立集(Independent Set):圖上的一個點集I使得點集I內任兩個點不相鄰。
    (邊獨立集=匹配)

點覆蓋

點獨立集

+=+=V  

而且在二分圖上 |最大匹配| = |最小點覆蓋|

(Konig定理)

二分圖最大匹配

Berge’s Lemma:
一組匹配 M 為圖 G 中最大匹配若且唯若圖上不存在對於 M 來說的增廣路徑。

增廣路徑

匹配邊:

二分圖的時候

想辦法把所有增廣路徑都消掉?

枚舉二分圖一側每個點都

試試看有沒有增廣路徑

正確性

對一側的某個點來說他最多找到一次增廣路徑

因為在找到增廣路徑之後他會成為匹配點
匹配點是不能找到增廣路徑的

int n,k,ans=0,vis[2001]={},match[2001]={};//n:存點數量 k:存邊數量 ans:存匹配數 vis[]:存是否被遍歷過 match[]:存匹配的點
vector<int> gra[2001];//儲存圖
int dfs(int pos){
	if(vis[pos])return 0;//已遍歷不做事
	vis[pos]=1;
	for(auto i:gra[pos]){
		if(vis[i])continue;//已遍歷不做事
		vis[i]=1;
		if(match[i]==0||dfs(match[i])){//遞迴式找增廣路徑
			match[pos]=i;
			match[i]=pos;
			return 1;//找到回傳
		}
	}
	return 0;
}
main(){
	for(int i=1;i<=n*2;i++){
		for(int j=1;j<=n*2;j++)vis[j]=0;
		if(match[i]==0)ans+=dfs(i);
	}
   
}

KM演算法

(匈牙利演算法)

匈牙利演算法是個用來求二分圖最大權完美匹配的演算法

因為演算法很大一部分是基於以前匈牙利數學家

Dénes Kőnig和Jenő Egerváry的工作之上建立起來的

故稱作匈牙利演算法(Hungarian algorithm)

 Harold Kuhn在1955年提出了此演算法
James Munkres在1977年回顧了此算法
並提出此演算法的複雜度為多項式時間
故也稱Kuhn–Munkres algorithm(KM)

問題介紹

給定一個兩邊各nn個節點的完全二分圖

(也可以以矩陣方式給)

每條邊(u,v)都有權重c(u,v)c(u, v)

找到一個權重最大的完美匹配

核心想法

給每個點一個點權\(l(u)\)

使得每個邊\((u,v)\)符合\(l(u)+l(v)\geq c(u,v)\)

當一個邊\((u,v)\)符合\(l(u)+l(v)= c(u,v)\)時

稱之為緊邊
對一個二分圖中的緊邊集合稱之為緊邊子圖
當緊邊子圖中找到完美匹配時
所求的最大權重即為\(\Sigma l(u)\)

演算法步驟

  1. 將二分圖左側的點\(u\)的點權改為與他相連的邊中邊權的最大值(為符合\(l(u)+l(v)\geq c(u,v)\))
  2. 若緊邊子圖的匹配尚未達成完美匹配
    則持續做以下兩步
  • 想辦法讓匹配變大(找到增廣路徑)
  • 增加緊邊

交錯路徑樹

緊邊

匹配邊

非緊邊

交錯路徑樹

緊邊

匹配邊

非緊邊

增加緊邊?

\(+\Delta\)

\(+\Delta\)

\(-\Delta\)

\(-\Delta\)

\(\Delta\)為藍邊中\(l(u)+l(v)-c(u,v)\)最小的

複雜度

對於\(N\)個點
每次加點進交錯路徑樹要作

\(O(N^2)\)的DFS
可能加\(N\)次
共\(O(N^4)\)

int vx[MAXN]={},vy[MAXN]={},mx[MAXN]={},my[MAXN]={},lx[MAXN]={},ly[MAXN]={};
//vx:左側記錄是否遍歷過 vy:右側紀錄是否遍歷過 mx:左側紀錄匹配點 my:右側記錄匹配點 lx:左側點權 ly:右側點權
int w[MAXN][MAXN],n;
int dfs(int pos){
	if(vx[pos])return 0;
	vx[pos]=1;
	for(int i=1;i<=n;i++){
		if(lx[pos]+ly[i]>w[pos][i])continue;//非緊邊不跑
		vy[i]=1;
		if(my[i]==0||dfs(my[i])){
			mx[pos]=i;//交錯路徑改匹配
			my[i]=pos;
			return 1;
		}
	}
	return 0;
}
int main(){
	cin>>n;
	while(1){
		if(n<=0)break;
		for(int i=1;i<=n;i++)mx[i]=0,my[i]=0,lx[i]=0,ly[i]=0;
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
			cin>>w[i][j];//輸入邊權
			if(w[i][j]<0)w[i][j]=0;
		}
		vx[0]=1;
		vy[0]=1;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				lx[i]=max(lx[i],w[i][j]);//設定左側點權
			}
		}
		for(int i=1;i<=n;i++){//遍歷點
			while(1){//找不到增廣路徑就一直跑
				for(int i=1;i<=n;i++)vx[i]=0,vy[i]=0;
				if(dfs(i)){//找到增廣路徑就跳出
					break;
				}
				int del=(1<<30);
				for(int j=1;j<=n;j++){
					if(!vx[j])continue;
					for(int k=1;k<=n;k++){
						if(!vy[k]){
							del=min(del,lx[j]+ly[k]-w[j][k]);//設定delta
						}
					}
				}
				for(int j=1;j<=n;j++){
					if(vx[j])lx[j]-=del;//改delta
					if(vy[j])ly[j]+=del;
				}
			}
		}
		int ans=0;
		for(int i=1;i<=n;i++){
			ans+=lx[i]+ly[i];//算答案
		}
		cout<<ans<<endl;
		cin>>n;
	}
}

應用

解決組合最佳化中的任務分配問題
(線性規劃的特例)

有一些員工要完成一些任務。
各個員工完成不同任務所花費的時間都不同。每個員工只分配一項任務。
每項任務只被分配給一個員工。
怎樣分配員工與任務以使所花費的時間最少?

感謝聆聽

二分圖匹配(離散

By linki1010111

二分圖匹配(離散

  • 331