圖論總結(一)二分圖最大匹配

二分圖最大匹配

(一)、二分圖

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

3、最大獨立集

(1)、POJ1466 Girls and Boys
(2)、POJ3692 Kindergarten

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章