二分圖匹配之最佳匹配——KM算法

今天也大體學了下KM算法,用於求二分圖匹配的最佳匹配。ios

何爲最佳?咱們能用匈牙利算法對二分圖進行最大匹配,但匹配的方式不惟一,若是咱們假設每條邊有權值,那麼必定會存在一個最大權值的匹配狀況,但對於KM算法的話這個狀況有點特殊,這個匹配狀況是要在徹底匹配(就是各個點都能一一對應另外一個點)狀況下的前提。算法

天然,KM算法跟匈牙利算法有類似之處。數組

其算法步驟以下:網絡

1.用鄰接矩陣(或其餘方法也行啦)來儲存圖,注意:若是隻是想求最大權值匹配而不要求是徹底匹配的話,請把各個不相連的邊的權值設置爲0ide

2.運用貪心算法初始化標杆。spa

3.運用匈牙利算法找到完備匹配。 code

4.若是找不到,則經過修改標杆,增長一些邊。 blog

5.重複3,4的步驟,直到徹底匹配時可結束。 string

一言不合地冒出了個標杆??標杆是什麼???io

在解釋這個問題以前,咱們先來假設一個很簡單的狀況,用咱們人類偉大的智能思惟去思考思考。

 


如上的一個二分圖,咱們要求它的最大權值匹配(最佳匹配)

咱們能夠思索思索

二分圖最佳匹配仍是二分圖匹配,因此跟和匈牙利算法思路差很少

二分圖是特殊的網絡流,最佳匹配至關於求最大(小)費用最大流,因此FF方法也能實現

因此咱們能夠把這匈牙利算法和FF方法結合起來

FF方法裏面,咱們每次是找最長(短)路進行通流

因此二分圖匹配裏面咱們也找最大邊進行連邊!

可是遇到某個點被匹配了兩次怎麼辦?

那就用匈牙利算法進行更改匹配

這就是KM算法的思路了:儘可能找最大的邊進行連邊,若是不能則換一條較大的。

因此,根據KM算法的思路,咱們一開始要對邊權值最大的進行連線,那問題就來了,咱們如何讓計算機知道該點對應的權值最大的邊是哪一條?或許咱們能夠經過某種方式

記錄邊的另外一端點,可是呢,後面還要涉及改邊,又要記錄邊權值總和,而這個記錄端點方法彷佛有點麻煩,因而KM採用了一種十分巧妙的辦法(也是KM算法思想的精髓):

添加標杆(頂標)

是怎樣子呢?咱們對左邊每一個點Xi和右邊每一個點Yi添加標杆Cx和Cy。

其中咱們要知足Cx+Cy>=w[x][y](w[x][y]即爲點Xi、Yi之間的邊權值)

對於一開始的初始化,咱們對於每一個點分別進行以下操做

Cx=max(w[x][y]);

Cy=0;

而後,咱們能夠進行連邊,即採用匈牙利算法,只是在判斷兩點之間是否有連線的條件下,由於咱們要將最大邊進行連線,因此原來判斷是否有邊的條件w[x][y]==0換成了

Cx+Cy==w[x][y]

此時,有一個新的名詞——相等子圖

由於咱們經過了巧妙的處理讓計算機自動鏈接邊權最大的邊,換句話說,其餘邊計算機就不會連了,也就「不存在」這個圖中,但咱們能夠隨時加上這些「不存在」圖中的邊。此時這個圖能夠認爲是原圖的子圖,而且是等效。

這樣,計算機在枚舉右邊的點的時候,知足以上條件,就可以知道這條邊是咱們要連的最大的邊,就能進行連邊了。

因而乎咱們連了AD。

接下來就尷尬了,計算機接下來要連B點的BD,可是D點已經和A點連了,怎麼辦呢???

根據匈牙利算法,咱們作的是將A點與其餘點進行連線,但此時的子圖裏「不存在」與A點相連的其餘邊,怎麼辦呢??

爲此,咱們就須要加上這些邊!

很明顯,咱們添邊,天然要加上不在子圖中邊權最大的邊,也就是和子圖裏這個邊權值差最小的邊。

因而,咱們再一度引入了一變量d,d=min{Cx[i]+Cy[j]-w[i][j]}

其中,在這個題目裏Cx[i]指的是A的標杆,Cy[j]是除D點(即已連點)之外的點的標杆。

隨後,對於原先存在於子圖的邊AD,咱們將A的標杆Cx[i]減去d,D的標杆Cy[d]加上d。

這樣,這就保證了原先存在AD邊保留在了子圖中,而且把不在子圖的最大權值的與A點相連的邊AE添加到了子圖。

由於計算機判斷一條邊是否在該子圖的條件是其兩端的頂點的標杆知足 

Cx+Cy==w[x][y]

對於原先的邊,咱們對左端點的標杆減去了d,對右端點的標杆加上了d,因此最終的結果仍是不變,仍然是w[x][y]。

對於咱們要添加的邊,咱們對於左端點減去了d,即Cx[i]=Cx[i]-d;爲方便表示咱們把更改後的的Cx[i]視爲Cz[i],即Cz[i]=Cx[i]-d;

對於右端點,咱們並無對其進行操做。那這條咱們要添加邊的兩端點的標號是否知足Cz[i]+Cy[j]=w[i][j]?

由於Cz[i]=Cx[i]-d;d=Cx[i]+Cy[j]-w[i][j];

咱們把d代入左式可得Cz[i]=Cx[i]-(Cx[i]+Cy[j]-w[i][j]);

化簡得Cz[i]+Cy[j]=w[i][j]。

知足了要求!即添加了新的邊。

值得注意的是,這裏咱們只是對於一條邊操做,當咱們添加了幾條邊,要進行如上操做時,要保證原先存在的邊不消失,那麼咱們就要先求出了d,而後

對於每一個連邊的左端點(記做集合S)的每一個點的標號減去了d以後,而後連邊的右端點(記做T)加上d,這樣就保證了原先的邊不消失啦~

實際上這就是一直在尋找着增廣路,經過不斷修改標杆進行添邊實現。

接下來就繼續着匈牙利算法,直到徹底匹配完爲止。

該算法的正確性就在於 它每次都選擇最大的邊進行連邊

至此,咱們再回顧KM算法的步驟:

 1.用鄰接矩陣(或其餘方法也行啦)來儲存圖。

2.運用貪心算法初始化標杆。

3.運用匈牙利算法找到完備匹配。 

4.若是找不到,則經過修改標杆,增長一些邊。 

5.重複3,4的步驟,直到徹底匹配時可結束。 

 

 是否是清楚了許多??

 

由於二分圖是網絡流的一種特殊狀況,在網絡流裏咱們是經過不斷的SPFA找到費用最大(小)的路徑進行通流,跟這個有點相似。

 

若是咱們要求邊權值最小的匹配呢???

咱們能夠把邊權值取負值,得出結果後再取相反數就能夠了。

至於爲何,正負大小相反了嘛~

至此,這大概是我我的的一點點理解了,但願對您有所幫助。

如有不當之處還請你們指出QwQ。 

 

 

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 using namespace std;
 5 const int qwq=0x7fffffff;
 6 int w[1000][1000];  //w數組記錄邊權值 
 7 int line[1000],usex[1000],usey[1000],cx[1000],cy[1000];  //line數組記錄右邊端點所連的左端點, usex,usey數組記錄是否曾訪問過,也是判斷是否在增廣路上,cx,cy數組就是記錄點的頂標 
 8 int n,ans,m;  //n左m右 
 9 bool find(int x){
10     usex[x]=1;
11     for (int i=1;i<=m;i++){
12         if ((usey[i]==0)&&(cx[x]+cy[i]==w[x][i])){   //若是這個點未訪問過而且它是子圖裏面的邊 
13             usey[i]=1;
14             if ((line[i]==0)||find(line[i])){   //若是這個點未匹配或者匹配點能更改 
15                 line[i]=x;
16                 return true;
17             }
18         }
19     }
20     return false;
21 }
22 int km(){
23     for (int i=1;i<=n;i++){  //分別對左邊點依次匹配 
24         while (true){
25         int d=qwq;
26         memset(usex,0,sizeof(usex));
27         memset(usey,0,sizeof(usey));
28         if (find(i)) break;  //直到成功匹配才換下一個點匹配 
29         for (int j=1;j<=n;j++)
30             if (usex[j])
31              for (int k=1;k<=m;k++)
32              if (!usey[k]) d=min(d,cx[j]+cy[k]-w[j][k]);  //計算d值 
33         if (d==qwq) return -1;  
34         for (int j=1;j<=n;j++)
35          if (usex[j]) cx[j]-=d;   
36         for (int j=1;j<=m;j++)
37          if (usey[j]) cy[j]+=d;     //添加新邊 
38        }
39     }
40     ans=0;
41     for (int i=1;i<=m;i++)
42      ans+=w[line[i]][i];
43     return ans;
44 }
45 int main(){
46     while (~scanf("%d%d",&n,&m)){
47     memset(cy,0,sizeof(cy));
48     memset(w,0,sizeof(w));
49     memset(cx,0,sizeof(cx));
50     for (int i=1;i<=n;i++){
51      int d=0;
52      for (int j=1;j<=n;j++){
53       scanf("%d",&w[i][j]);
54       d=max(d,w[i][j]);   //此處順便初始化左邊點的頂標 
55        }
56     cx[i]=d;
57     }
58     memset(line,0,sizeof(line));
59     printf("%d\n",km());
60 }
61     return 0;
62 }
KM算法
相關文章
相關標籤/搜索