二分圖匹配
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)\)
演算法步驟
- 將二分圖左側的點\(u\)的點權改為與他相連的邊中邊權的最大值(為符合\(l(u)+l(v)\geq c(u,v)\))
- 若緊邊子圖的匹配尚未達成完美匹配
則持續做以下兩步
- 想辦法讓匹配變大(找到增廣路徑)
- 增加緊邊
交錯路徑樹
緊邊
匹配邊
非緊邊
交錯路徑樹
緊邊
匹配邊
非緊邊
增加緊邊?
\(+\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