匈牙利算法解決二分圖最大匹配


預備知識

  匈牙利算法是由匈牙利數學家Edmonds於1965年提出,因而得名。匈牙利算法是基於Hall定理中充分性證明的思想,它是二分圖匹配最常見的算法,該算法的核心就是尋找增廣路徑,它是一種用增廣路徑求二分圖最大匹配的算法。

二分圖

  二分圖又稱作二部圖,是圖論中的一種特殊模型。 設G=(V,E)是一個無向圖,如果頂點V可分割為兩個互不相交的子集(A,B),並且圖中的每條邊(i,j)所關聯的兩個頂點 i 和 j 分別屬於這兩個不同的頂點集(i in A,j in B),則稱圖G為一個二分圖。

匹配

  在圖論中,一個圖是一個匹配(或稱獨立邊集)是指這個圖之中,任意兩條邊都沒有公共的頂點。這時每個頂點都至多連出一條邊,而每一條邊都將一對頂點相匹配。

  例如,圖3、圖4中紅色的邊就是圖2的匹配。

  圖3中1、4、5、7為匹配點,其他頂點為非匹配點,1-5、4-7為匹配邊,其他邊為非匹配邊。

最大匹配

  一個圖所有匹配中,所含匹配邊數最多的匹配,稱為這個圖的最大匹配。圖 4 是一個最大匹配,它包含 4 條匹配邊。

  任意圖中,極大匹配的邊數不少於最大匹配的邊數的一半。

完美匹配

  如果一個圖的某個匹配中,所有的頂點都是匹配點,那么它就是一個完美匹配。顯然,完美匹配一定是最大匹配,但並非每個圖都存在完美匹配。

最大匹配數:最大匹配的匹配邊的數目。

最小點覆蓋數:選取最少的點,使任意一條邊至少有一個端點被選擇。

最大獨立數:選取最多的點,使任意所選兩點均不相連。

最小路徑覆蓋數:對於一個DAG(有向無環圖),選取最少條路徑,使得每個頂點屬於且僅屬於一條路徑,路徑長可以為0(即單個點)

定理1:Konig定理——最大匹配數 = 最小點覆蓋數

定理2:最大匹配數 = 最大獨立數

定理3:最小路徑覆蓋數 = 頂點數 - 最大匹配數

 

匈牙利算法

例子

   為了便於理解,選取了dalao博客里找妹子的例子

    https://blog.csdn.net/dark_scope/article/details/8880547

   通過數代人的努力,你終於趕上了剩男剩女的大潮,假設你是一位光榮的新世紀媒人,在你的手上有N個剩男,M個剩女,每個人都可能對多名異性有好感(驚訝,-_-||暫時不考慮特殊的性取向)

  如果一對男女互有好感,那么你就可以把這一對撮合在一起,現在讓我們無視掉所有的單相思(好憂傷的感覺,快哭了),你擁有的大概就是下面這樣一張關系圖,每一條連線都表示互有好感。

  

  本着救人一命,勝造七級浮屠的原則,你想要盡可能地撮合更多的情侶,匈牙利算法的工作模式會教你這樣做:

  一: 先試着給1號男生找妹子,發現第一個和他相連的1號女生還名花無主,got it,連上一條藍線

  

  二:接着給2號男生找妹子,發現第一個和他相連的2號女生名花無主,got it

  

  三:接下來是3號男生,很遺憾1號和2號女生已經有主了,怎么辦呢?

  我們試着給之前1號女生匹配的男生(也就是1號男生)另外分配一個妹子

  與1號男生相連的第二個女生是2號女生,但是2號女生也有主了,怎么辦呢?

  我們再試着給2號女生的原配(嚶嚶嚶),重新找個妹子(注意這個步驟和上面是一樣的,這是一個遞歸的過程)

  此時發現2號男生還能找到3號女生,那么之前的問題迎刃而解了,回溯回去。

  

  四:接下來是4號男生,很遺憾,按照第三步的節奏我們沒法給4號男生騰出來一個妹子,我們實在是無能為力了……交替路:從一個未匹配點出發,依次經過非匹配邊、匹配邊、非匹配邊...形成的路徑叫交替路。

  這時,想引入交替路徑和增廣路徑的概念:

  交替路:從一個未匹配點出發,依次經過非匹配邊、匹配邊、非匹配邊...形成的路徑。

  增廣路:由一個未匹配的頂點開始,經過若干個匹配頂點(走交替路),最后到達對面集合的一個未匹配頂點的路徑,則這條交替路稱為增廣路(agumenting path)

   舉例來說,有A、B集合,增廣路由A中一個點通向B中一個點,再由B中這個點通向A中一個點……交替進行。

  

  增廣路徑將兩個不同集合的兩個未匹配頂點通過一系列匹配頂點相連。

  求最大匹配的一種顯而易見的算法是:先找出全部匹配,然后保留匹配數最多的。但是這個算法的時間復雜度為邊數的指數級函數。因此,需要尋求一種更加高效的算法。

  我們可以通過用增廣路徑求最大匹配,即匈牙利法

匈牙利法的思想

  增廣路徑的首尾是非匹配點。因此,增廣路徑的第一條和最后一條邊,必然是非匹配邊;同時它的第二條邊和倒數第二條邊,必然是匹配邊;以及第三條邊(如果有)和倒數第三條邊(如果有),一定是非匹配邊。這樣一來,增廣路徑中非匹配邊數 = 匹配邊數 + 1

  如果我們置換增廣路徑中的匹配邊和非匹配邊,由於增廣路徑的首尾是非匹配點,其余則是匹配點,這樣的置換不會影響原匹配中其他的匹配邊和匹配點,因而不會破壞匹配,可以得到比原有匹配更大的匹配(具體來說,匹配的邊數增加了 1)。

  

  由於二分圖的最大匹配必然存在(比如,上限是包含所有頂點的完全匹配),所以,再任意匹配的基礎上,如果我們有辦法不斷地搜尋出增廣路徑,直到最終我們找不到新的增廣路徑為止,我們就有可能得到二分圖的一個最大匹配。這,就是匈牙利法的思想。

  但是,基於這種貪心的思想,你又如何能證明它是可以找到最大匹配的解?

  dalao給出以下證明:

  我們從反證法考慮,即假設存當前匹配不是二分圖的最大匹配,但已找不到一條新的增廣路徑的情況。因為當前匹配不是二分圖的最大匹配,那么在兩個集合中,分別至少存在一個非匹配點。那么情況分為兩種:

  1. 這兩個點之間存在一條邊——那么我們找到了一條新的增廣路徑,產生矛盾;

  2. 這兩個點之間不存在直接的邊,即這兩個點分別都只與匹配點相連——那么:

    2.1 如果這兩個點可以用已有的匹配點相連,那么我們找到了一條新的增廣路徑,產生矛盾;

    2.2 如果這兩個點無法用已有的匹配點相連,那么這兩個點也就無法增加匹配中邊的數量,也就是我們已經找到了二分圖的最大匹配,產生矛盾。

  在所有可能的情況,上述假設都會產生矛盾。因此假設不成立,亦即貪心算法必然能求得最大匹配的解。

參考地址:https://blog.csdn.net/ctsas/article/details/62421389

 

HDOJ2063

  我們以HDOJ2063為例。 http://acm.hdu.edu.cn/showproblem.php?pid=2063

過山車

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 33350    Accepted Submission(s): 14266

  Problem Description
  RPG girls今天和大家一起去游樂場玩,終於可以坐上夢寐以求的過山車了。可是,過山車的每一排只有兩個座位,而且還有條不成文的規矩,就是每個女生必須找個男生做partner和她同坐。但是,每個女孩都有各自的想法,舉個例子把,Rabbit只願意和XHD或PQK做partner,Grass只願意和linle或LL做partner,PrincessSnow願意和水域浪子或偽酷兒做partner。考慮到經費問題,boss劉決定只讓找到partner的人去坐過山車,其他的人,嘿嘿,就站在下面看着吧。聰明的Acmer,你可以幫忙算算最多有多少對組合可以坐上過山車嗎?
 
  Input
  輸入數據的第一行是三個整數K , M , N,分別表示可能的組合數目,女生的人數,男生的人數。0<K<=1000,1<=N 和M<=500.
  接下來的K行,每行有兩個數,分別表示女生Ai願意和男生Bj做partner。最后一個0結束輸入。
 
  Output
  對於每組數據,輸出一個整數,表示可以坐上過山車的最多組合數。
 
  Sample Input
6 3 3
1 1
1 2
1 3
2 1
2 3
3 1
0
  Sample Output
  3

 

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f;
using namespace std;
int L[505][505];
int boy[505];
int used[505];
int k,m,n;  //可能的組合數k,女生數m,男生數n 
bool find(int i){
    for(int j=1;j<=n;j++){
        if(L[i][j] && !used[j]){  //跟他有關系而且沒有搜索過 
            used[j]=1;
            if(!boy[j] || find(boy[j])){
                boy[j]=i;
                return true;
            }
        }
    }
    return false;
}
int main(){
    while(cin>>k>>m>>n){
        memset(L,0,sizeof(L));
        memset(boy,0,sizeof(boy));
        for(int i=1;i<=k;i++){
            int n1,n2;
            cin>>n1>>n2;
            L[n1][n2]=1;
        }
        int sum=0;
        for(int i=1;i<=m;i++){
            memset(used,0,sizeof(used));
            if(find(i)) sum++;
        }
        cout<<sum<<endl;
    }
    return 0;
} 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM