今天也大致學了下KM算法,用於求二分圖匹配的最佳匹配。
何為最佳?我們能用匈牙利算法對二分圖進行最大匹配,但匹配的方式不唯一,如果我們假設每條邊有權值,那么一定會存在一個最大權值的匹配情況,但對於KM算法的話這個情況有點特殊,這個匹配情況是要在完全匹配(就是各個點都能一一對應另一個點)情況下的前提。
自然,KM算法跟匈牙利算法有相似之處。
其算法步驟如下:
1.用鄰接矩陣(或其他方法也行啦)來儲存圖,注意:如果只是想求最大權值匹配而不要求是完全匹配的話,請把各個不相連的邊的權值設置為0。
2.運用貪心算法初始化標桿。
3.運用匈牙利算法找到完備匹配。
4.如果找不到,則通過修改標桿,增加一些邊。
5.重復3,4的步驟,直到完全匹配時可結束。
一言不合地冒出了個標桿??標桿是什么???
在解釋這個問題之前,我們先來假設一個很簡單的情況,用我們人類偉大的智能思維去思考思考。

如上的一個二分圖,我們要求它的最大權值匹配(最佳匹配)
我們可以思索思索
二分圖最佳匹配還是二分圖匹配,所以跟和匈牙利算法思路差不多
二分圖是特殊的網絡流,最佳匹配相當於求最大(小)費用最大流,所以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 }
