關於二分圖的講解

原文鏈接

二分圖匹配,自然要先從定義入手,那麼二分圖是什麼呢?

二分圖:

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

簡單的說,一個圖被分成了兩部分,相同的部分沒有邊,那這個圖就是二分圖,二分圖是特殊的圖。

匹配:

給定一個二分圖G,在G的一個子圖M中,M的邊集{E}中的任意兩條邊都不依附於同一個頂點,則稱M是一個匹配。
極大匹配(Maximal Matching)是指在當前已完成的匹配下,無法再通過增加未完成匹配的邊的方式來增加匹配的邊數。最大匹配(maximum matching)是所有極大匹配當中邊數最大的一個匹配。選擇這樣的邊數最大的子集稱爲圖的最大匹配問題。
如果一個匹配中,圖中的每個頂點都和圖中某條邊相關聯,則稱此匹配爲完全匹配,也稱作完備匹配。
求二分圖匹配可以用最大流(Maximal Flow)或者匈牙利算法(Hungarian Algorithm)

注意匈牙利算法,除了二分圖多重匹配外在二分圖匹配中都可以使用。

注:二分圖匹配中還有一個hk算法,複雜度爲o(sqrt(n)*e)由於複雜度降低較低,代碼量飆升而且絕大多數情況下沒人會閒的卡個sqrt的複雜度。。在此先不講了,有興趣可以自己百度,貌似卡這個算法的只有hdu2389

嘛 首先我們講解一下匈牙利算法的過程:

匈牙利算法:

匈牙利算法幾乎是二分圖匹配的核心算法,除了二分圖多重匹配外均可使用

匈牙利算法實際上就是一種網絡流的思想,其核心就是尋找增廣路。具體操作就是嗯。。拉郎配

注:以下轉自 http://blog.csdn.NET/dark_scope/article/details/8880547

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

-------等等,看得頭大?那麼請看下面的版本:

通過數代人的努力,你終於趕上了剩男剩女的大潮,假設你是一位光榮的新世紀媒人,在你的手上有N個剩男,M個剩女,每個人都可能對多名異性有好感(驚訝-_-||暫時不考慮特殊的性取向),如果一對男女互有好感,那麼你就可以把這一對撮合在一起,現在讓我們無視掉所有的單相思(好憂傷的感覺快哭了),你擁有的大概就是下面這樣一張關係圖,每一條連線都表示互有好感。


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

===============================================================================

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


===============================================================================

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


===============================================================================

接下來是3號男生,很遺憾1號女生已經有主了,怎麼辦呢?

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

(黃色表示這條邊被臨時拆掉)

與1號男生相連的第二個女生是2號女生,但是2號女生也有主了,怎麼辦呢?我們再試着給2號女生的原配(發火發火)重新找個妹子(注意這個步驟和上面是一樣的,這是一個遞歸的過程)


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

2號男生可以找3號妹子~~~                  1號男生可以找2號妹子了~~~                3號男生可以找1號妹子

所以第三步最後的結果就是:


===============================================================================

 接下來是4號男生,很遺憾,按照第三步的節奏我們沒法給4號男生出來一個妹子,我們實在是無能爲力了……香吉士同學走好。

===============================================================================

這就是匈牙利算法的流程,其中找妹子是個遞歸的過程,最最關鍵的字就是“”字

其原則大概是:有機會上,沒機會創造機會也要上


 hdu2063:

hdu2063就是一道裸二分圖最大匹配的問題,直接上匈牙利算法即可。

  1. #include<cstdio>  
  2. #include<cstring>  
  3. #include<iostream>  
  4. #include<queue>  
  5. #include<vector>  
  6. #include<cmath>  
  7. #include<algorithm>  
  8. using namespace std;  
  9. const int N=505;  
  10. int line[N][N];  
  11. int girl[N],used[N];  
  12. int k,m,n;  
  13. bool found(int x)  
  14. {  
  15.     for(int i=1; i<=n; i++)  
  16.     {  
  17.         if(line[x][i]&&!used[i])  
  18.         {  
  19.             used[i]=1;  
  20.             if(girl[i]==0||found(girl[i]))  
  21.             {  
  22.                 girl[i]=x;  
  23.                 return 1;  
  24.             }  
  25.         }  
  26.     }  
  27.     return 0;  
  28. }  
  29. int main()  
  30. {  
  31.     int x,y;  
  32.     while(scanf("%d",&k)&&k)  
  33.     {  
  34.         scanf("%d %d",&m,&n);  
  35.         memset(line,0,sizeof(line));  
  36.         memset(girl,0,sizeof(girl));  
  37.         for(int i=0; i<k; i++)  
  38.         {  
  39.             scanf("%d %d",&x,&y);  
  40.             line[x][y]=1;  
  41.         }  
  42.         int sum=0;  
  43.         for(int i=1; i<=m; i++)  
  44.         {  
  45.             memset(used,0,sizeof(used));  
  46.             if(found(i)) sum++;  
  47.         }  
  48.         printf("%d\n",sum);  
  49.     }  
  50.     return 0;  
  51. }  
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=505;
int line[N][N];
int girl[N],used[N];
int k,m,n;
bool found(int x)
{
    for(int i=1; i<=n; i++)
    {
        if(line[x][i]&&!used[i])
        {
            used[i]=1;
            if(girl[i]==0||found(girl[i]))
            {
                girl[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    int x,y;
    while(scanf("%d",&k)&&k)
    {
        scanf("%d %d",&m,&n);
        memset(line,0,sizeof(line));
        memset(girl,0,sizeof(girl));
        for(int i=0; i<k; i++)
        {
            scanf("%d %d",&x,&y);
            line[x][y]=1;
        }
        int sum=0;
        for(int i=1; i<=m; i++)
        {
            memset(used,0,sizeof(used));
            if(found(i)) sum++;
        }
        printf("%d\n",sum);
    }
    return 0;
}

二分圖最優匹配:

接下來就是二分圖最優匹配的km算法了

km算法理解起來着實很困難,我其實只能照着代碼講,不然根本講不明白。不過聽一個學長說要理解思想而不是代碼。。。那就試着空講一下吧。

一般對KM算法的描述,基本上可以概括成以下幾個步驟: 
(1) 初始化可行標杆 
(2) 用匈牙利算法尋找完備匹配 
(3) 若未找到完備匹配則修改可行標杆 
(4) 重複(2)(3)直到找到相等子圖的完備匹配 

關於該算法的流程及實施,網上有很多介紹,基本上都是圍繞可行標杆如何修改而進行的討論,至於原理並沒有給出深入的探討。 

KM算法是用於尋找帶權二分圖最佳匹配的算法。 

二分圖是這樣一種圖:所有頂點可以分成兩個集:X和Y,其中X和Y中的任意兩個在同一個集中的點都不相連,而來自X集的頂點與來自Y集的頂點有連線。當這些連線被賦於一定的權重時,這樣的二分圖便是帶權二分圖。 

二分圖匹配是指求出一組邊,其中的頂點分別在兩個集合中,且任意兩條邊都沒有相同的頂點,這組邊叫做二分圖的匹配,而所能得到的最大的邊的個數,叫做二分圖的最大匹配。 

我們也可以換個角度看二分圖的最大匹配,即二分圖的每條邊的默認權重爲1,我們求到的二分圖的最大匹配的權重最大。對於帶權二分圖,其邊有大於0的權重,找到一組匹配,使其權重最大,即爲帶權二分圖的最佳匹配。 

匈牙利算法一般用於尋找二分圖的最大匹配。算法根據一定的規則選擇二分圖的邊加入匹配子圖中,其基本模式爲: 

初始化匹配子圖爲空 
While 找得到增廣路徑 
Do 把增廣路徑添加到匹配子圖中 

增廣路徑有如下特性: 
1. 有奇數條邊 
2. 起點在二分圖的X邊,終點在二分圖的Y邊 
3. 路徑上的點一定是一個在X邊,一個在Y邊,交錯出現。 
4. 整條路徑上沒有重複的點 
5. 起點和終點都是目前還沒有配對的點,其他的點都已經出現在匹配子圖中 
6. 路徑上的所有第奇數條邊都是目前還沒有進入目前的匹配子圖的邊,而所有第偶數條邊都已經進入目前的匹配子圖。奇數邊比偶數邊多一條邊 
7. 於是當我們把所有第奇數條邊都加到匹配子圖並把條偶數條邊都刪除,匹配數增加了1. 

例如下圖,藍色的是當前的匹配子圖,目前只有邊x0y0,然後通過x1找到了增廣路徑:x1y0->y0x0->x0y2 

 

其中第奇數第邊x1y0和x0y2不在當前的匹配子圖中,而第偶數條邊x0y0在匹配子圖中,通過添加x1y0和x0y2到匹配子圖並刪除x0y0,使得匹配數由1增加到了2。每找到一條增廣路徑,通過添加刪除邊,我們總是能使匹配數加1. 

增廣路徑有兩種尋徑方法,一個是深搜,一個是寬搜。例如從x2出發尋找增廣路徑,如果是深搜,x2找到y0匹配,但發現y0已經被x1匹配了,於是就深入到x1,去爲x1找新的匹配節點,結果發現x1沒有其他的匹配節點,於是匹配失敗,x2接着找y1,發現y1可以匹配,於是就找到了新的增廣路徑。如果是寬搜,x1找到y0節點的時候,由於不能馬上得到一個合法的匹配,於是將它做爲候選項放入隊列中,並接着找y1,由於y1已經匹配,於是匹配成功返回了。相對來說,深搜要容易理解些,其棧可以由遞歸過程來維護,而寬搜則需要自己維護一個隊列,並對一路過來的路線自己做標記,實現起來比較麻煩。 

對於帶權重的二分圖來說,我們可以把它看成一個所有X集合的頂點到所有Y集合的頂點均有邊的二分圖(把原來沒有的邊添加入二分圖,權重爲0即可),也就是說它必定存在完備匹配(即其匹配數爲min(|X|,|Y|))。爲了使權重達到最大,我們實際上是通過貪心算法來選邊,形成一個新的二分圖(我們下面叫它二分子圖好了),並在該二分圖的基礎上尋找最大匹配,當該最大匹配爲完備匹配時,我們可以確定該匹配爲最佳匹配。(在這裏我們如此定義最大匹配:匹配邊數最多的匹配和最佳匹配:匹配邊的權重和最大的匹配。) 

貪心算法總是將最優的邊優先加入二分子圖,該最優的邊將對當前的匹配子圖帶來最大的貢獻,貢獻的衡量是通過標杆來實現的。下面我們將通過一個實例來解釋這個過程。 

有帶權二分圖: 

 
算法把權重轉換成標杆,X集跟Y集的每個頂點各有一個標杆值,初始情況下權重全部放在X集上。由於每個頂點都將至少會有一個匹配點,貪心算法必然優先選擇該頂點上權重最大的邊(最理想的情況下,這些邊正好沒有交點,於是我們自然得到了最佳匹配)。最初的二分子圖爲:(可以看到初始化時X標杆爲該頂點上的最大權重,而Y標杆爲0) 

 
從X0找增廣路徑,找到X0Y4;從X1找不到增廣路徑,也就是說,必須往二分子圖裏邊添加新的邊,使得X1能找到它的匹配,同時使權重總和添加最大。由於X1通往Y4而Y4已經被X0匹配,所以有兩種可能,一個是爲X0找一個新的匹配點並把Y4讓給X1,或者是爲X1找一個新的匹配點,現在我們將要看到標杆的作用了。根據傳統的算法描述,能夠進入二分子圖的邊的條件爲L(x)+L(y)>=weight(xy)。當找不到增廣路徑時,對於搜索過的路徑上的XY點,設該路徑上的X頂點集爲S,Y頂點集爲T,對所有在S中的點xi及不在T中的點yj,計算d=min{(L(xi)+L(yj)-weight(xiyj))},從S集中的X標杆中減去d,並將其加入到T集中的Y的標杆中,由於S集中的X標杆減少了,而不在T中的Y標杆不變,相當於這兩個集合中的L(x)+L(y)變小了,也就是,有新的邊可以加入二分子圖了。從貪心選邊的角度看,我們可以爲X0選擇新的邊而拋棄原先的二分子圖中的匹配邊,也可以爲X1選擇新的邊而拋棄原先的二分子圖中的匹配邊,因爲我們不能同時選擇X0Y4和X1Y4,因爲這是一個不合法匹配,這個時候,d=min{(L(xi)+L(yj)-weight(xiyj))}的意義就在於,我們選擇一條新的邊,這條邊將被加入匹配子圖中使得匹配合法,選擇這條邊形成的匹配子圖,將比原先的匹配子圖加上這條非法邊組成的非法匹配子圖的權重和(如果它是合法的,它將是最大的)小最少,即權重最大了。好繞口的。用數學的方式表達,設原先的不合法匹配(它的權重最大,因爲我們總是從權重最大的邊找起的)的權重爲W,新的合法匹配爲W’,d爲min{W-W’i}。在這個例子中,S={X0, X1},Y={Y4},求出最小值d=L(X1)+L(Y0)-weight(X1Y0)=2,得到新的二分子圖: 

 
重新爲X1尋找增廣路徑,找到X1Y0,可以看到新的匹配子圖的權重爲9+6=15,比原先的不合法的匹配的權重9+8=17正好少d=2。 
接下來從X2出發找不到增廣路徑,其走過的路徑如藍色的路線所示。形成的非法匹配子圖:X0Y4,X1Y0及X2Y0的權重和爲22。在這條路徑上,只要爲S={X0,X1,X2}中的任意一個頂點找到新的匹配,就可以解決這個問題,於是又開始求d。 
d=L(X0)+L(Y2)-weight(X0Y2)=L(X2)+L(Y1)-weight(X2Y1)=1. 
新的二分子圖爲: 

 

重新爲X2尋找增廣路徑,如果我們使用的是深搜,會得到路徑:X2Y0->Y0X1->X1Y4->Y4X0->X0Y2,即奇數條邊而刪除偶數條邊,新的匹配子圖中由這幾個頂點得到的新的權重爲21;如果使用的是寬搜,會得到路徑X2Y1,另上原先的兩條匹配邊,權重爲21。假設我們使用的是寬搜,得到的新的匹配子圖爲: 

 
接下來依次類推,直到爲X4找到一個匹配點。 

KM算法的最大特點在於利用標杆和權重來生成一個二分子圖,在該二分子圖上面找最大匹配,而且,當些僅當找到完備匹配,才能得到最佳匹配。標杆和權重的作用在於限制新邊的加入,使得加入的新邊總是能爲子圖添加匹配數,同時又令權重和得到最大的提高。

二分圖多重匹配:

設源點匯點直接網絡流即可,在此不細講,會在網絡流部分進行講解
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章