二分圖最大匹配
(一)、二分圖
1、定義
二分圖又稱作二部圖,是圖論中的一種特殊模型。
設G=(V, E)是一個無向圖。如果頂點集 V可分割爲兩個互不相交的子集X和Y,並且圖中每條邊連接的兩個頂點一個在 X中,另一個在 Y中,則稱圖G爲二分圖。
2、性質
定理:當且僅當無向圖G的每一個環
的結點數均是偶數時,圖G纔是一個二分圖。如果無環,相當於每的結點數爲 0,故也視爲二分圖。
3、判定
如果一個圖是連通的,可以用如下的染色法判定是否二分圖:
我們把X部的結點顏色設爲0,Y部的顏色設爲1。
從某個未染色的結點u開始,做BFS或者DFS 。把u染爲0,枚舉u的兒子v。如果v未染色,就染爲與u相反的顏色,如果已染色,則判斷u與v的顏色是否相同,相同則不是二分圖。
如果一個圖不連通,則在每個連通塊中作判定。
代碼如下:
#include<cstdio>
#include<queue>
using namespace std;
#define MAX_N 100
bool flag=true;//記錄答案
bool G[MAX_N][MAX_N];//鄰接矩陣
int m,n,Col[MAX_N+5];//記錄邊數、結點數和結點的顏色
queue<int>Q;//定義隊列進行拓展
void Bfs(int x){
Q.push(x);//初始結點入隊
Col[x]=0;//顏色爲0
while(!Q.empty()){
int p=Q.front();
Q.pop();
for(int i=1;i<=n;i++)
if(G[p][i]){//枚舉相連結點
if(Col[i]==Col[p])//根據判定,相同顏色則不爲二分圖
flag=false;//記錄答案
else if(Col[i]==-1){
Col[i]=!Col[p];//與之相反的顏色
Q.push(i);//入隊拓展
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
G[u][v]=G[v][u]=true;
}
memset(Col,-1,sizeof Col);//初始狀態都爲-1
for(int i=1;i<=n;i++)//需要枚舉每個結點,考慮多個連通塊的情況
if(Col[i]==-1)
Bfs(i);
if(!flag) puts("False");
else puts("True");
}
(二)、二分圖的匹配
1、二分圖的最大匹配
給定一個二分圖G,在G的一個子圖M中, M的邊集{E}中的任意兩條邊都不交匯於同一個結點,則稱M是一個匹配。
圖中加粗的邊是數量爲2的匹配。
選擇邊數最大的子圖稱爲最大匹配問題
如果一個匹配中,圖的每個頂點 都和圖中某條邊相關聯,則稱此匹配爲完全匹配 ,也稱作完備匹配。
圖中所示爲最大匹配,但不是完全匹配
2、 König定理及其證明
最小覆蓋點數=最大匹配數
證明:首先,我們要抓住二分圖最大匹配後的特點,此時,不存在增廣路。如上圖所示,該圖爲不完整的最大匹配後二分圖。
紅點爲匹配點,藍點未匹配。
對於一個點而言,他所連接的有這三種情況:
1、只連接了紅點;
2、只連接了藍點;
3、連接了紅點和藍。
在上面的圖中,x與y所連接的邊是匹配邊。x連接了紅點和藍點, 這個時候y所連接的點一 所連接的點一定沒有藍點,如果有,就是一條新的增廣路那麼該圖就不是最大匹配了。
在最大匹配圖中不會出現一條邊同時連接着兩個藍點。那麼,對於一條邊而言只有兩種情況:
1、兩端的點是紅點;
2、兩端的點一個紅色,一個藍色。
可知,一條邊上,一定有紅點,那麼我們就選擇紅點作爲覆蓋點。
對於上面的匹配邊xy,我們無論是選擇x還是y都可以覆蓋xy這條邊, 但是對於圖中的藍點而言這條邊, 但是對於圖中的藍點而言這條邊, 但是對於圖中的藍點而言這條邊, 但是對於圖中的藍點而言這條邊,只能選擇x作爲覆蓋點去覆蓋那條邊,這樣,我們就選擇x作爲覆蓋點,它所覆蓋的邊中既包括了與他相連的那些藍點的邊,也包括了xy這條匹配邊。因爲y點沒有藍色連接點,所以y不是必須選擇的覆蓋點,它與那些紅點相連的邊都可以選擇那些紅點來覆蓋。所以,對於一條匹配邊而言,我們只需要選擇其中一個點就可以覆蓋完整個二分圖裏的邊了。
所以最小覆蓋點數等於大匹配數。
3、最小邊覆蓋與最大獨立集
最小邊覆蓋=最大獨立集=總節點數-最大匹配數
(三)、增廣路徑
1、定義
增廣路徑的定義:設M爲二分圖G**已匹配邊的集合,若P是圖G中一條連通兩個未匹配頂點的路徑(P的起點在X部,終點在Y部,反之亦可),並且屬M的邊和不屬M的邊 (即已匹配和待的邊)在P上交替出現**,則稱P爲相對於M的一條增廣路徑。
增廣路徑是一條“交錯軌”。也就說 , 它的第一條邊是目前還沒有參與匹配的 ,第二條邊參與了匹配 ,第三條邊沒有······最後一條邊沒有參與匹配 ,並且起點和終還沒有被選擇過,這樣交錯進行 ,顯然 P有奇數條邊。
2、性質
由增廣路的定義可以推出下述三個結論:
(1)P的路徑長度必定爲奇數,第一條邊和最後一條邊都不屬於M,因爲兩個端點分屬兩個集合,且未匹配。
(2)P經過取反操作可以得到一個更大的匹配M’ 。
(3)M爲G的最大匹配當且僅當不存在相對於M的增廣路徑。
3、尋找增廣路
紅邊爲三條已經匹配的邊。從X部一個未匹配的頂點x4 開始,找一條路徑:找一條路徑:
x4, y3, x2, y1, x1, y2
因爲y2是Y部中未匹配的頂點,故所找路徑是增廣路徑。
其中有屬於匹配M的邊爲 {x2,y3},{x1,y1}
不屬於匹配的邊爲{x4,y3},{x2, y1}, x1,y2}
可以看出:不屬於匹配的邊要多一條
如果從 M中抽走 {x2,y3},{x1,y1}並加入
{x4,y3},{x2, y1}, {x1,y2},也就是將增廣路所有的邊進行 “反色”,則可以得到四條邊的匹配M’={{x3,y4}, {x4,y3},{x2, y1},{x1,y2}}
容易發現這樣修改以後,匹配仍然是合法的,但是匹配數增加了一對。另外,單獨的一條連接兩個未匹配點邊顯然也是交錯軌。可以證明 ,當不能再找到增廣軌時,就得到了一個最大匹配。這也就是匈牙利算法的思路。
可知四條邊的匹配是最大匹配。
(四)、匈牙利算法
1、找增廣路經的算法
我們採用DFS的辦法找一條增廣路徑:
從X部一個未匹配的頂點u開始,找一個未訪問的鄰接點v(v一定是Y部頂點)。對於 v,分兩種情況:
(1)如果v未匹配,則已經找到一條增廣路。
(2)如果v已經匹配,則取出v的匹配頂點w(w一定是X部頂點),邊 (w,v)目前是匹配的,根據“取反”的想法,要將(w,v)改爲未匹配, (u,v)設爲匹配,能實現這一點的條件是看從w爲起點能否新找到一條增廣路徑P’ 。如果行,則u-v-P’ 就是一條以u爲起點的增廣路徑。 爲起點的增廣路徑。
2、實踐
匈牙利算法
/************匈牙利算法**************/
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
#define MAX_N 512
vector<int>Adj[MAX_N];
int n,m,ans;
void AddEdge(int u,int v){
Adj[u].push_back(v);
Adj[v].push_back(u);
}
/**********讀入數據,建圖*************/
void Init(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int si,k;
scanf("%d",&si);
for(int j=1;j<=si;j++){
scanf("%d",&k);
k+=n;
AddEdge(i,k);
}
}
}
/************深搜找增廣路************/
bool Vis[MAX_N+1];
int Match[MAX_N+1];
bool Dfs(int u){
for(int i=0;i<Adj[u].size();i++){
int v=Adj[u][i];
if(Vis[v])
continue;
Vis[v]=true;
if(!Match[v]||Dfs(Match[v])){
Match[v]=u;
Match[u]=v;
return true;
}
}
return false;
}
/*********匈牙利算法主函數**********/
void Solve(){
for(int i=1;i<=n;i++){
memset(Vis,false,sizeof Vis);
if(!Match[i])
if(Dfs(i))
ans++;
}
}
int main(){
Init();
Solve();
printf("%d\n",ans);
}
3、算法分析
算法的核心是找增廣路徑過程DFS
對於每個可以與u匹配的頂點v,假如它未被匹配,可以直接用 v與u匹配;
如果v已與頂點w匹配,那麼只需調用DFS(w)來求證w是否可以與其它頂點匹配,如果DFS(w)返回 true的話,仍可以使v與u匹配;如果DFS(w)返回false,則檢查u的下一個鄰接點 ······
在DFS 時,要標記訪問過的頂點(vis[j]=true),以防死循環和重複計算;每次在主過程中開始一次DFS前,所有的頂點都是未標記。
主過程只需對每個X部的頂點調用DFS ,如果返回一次 true,就對最大匹配數加一;一個簡單的循環就求出了最大匹配數目。
時空分析
時間複雜度:
找一次增廣路徑的時間爲:
鄰接矩陣: O(n^2)
鄰接表: O( n+m)
總時間:
鄰接矩陣: O(n^3)
鄰接表: O(nm)
空間複雜度:
鄰接矩陣: O(n^2)
鄰接表: O( m+n)
(五)、例題
1、最小點覆蓋
(1)、POJ1325 Machine Schedule
(2)、POJ3041 Asteroids
(3)、POJ2226 Muddy Fields
2、最小邊覆蓋
(1)、POJ2724 Purifying Machine
(2)、POJ3020 Antenna Placement