[SinGuLaRiTy] KM算法
【SinGuLaRiTy-1018】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.
Some Method Are Reproduced From?evasiu。
KM算法的基本步驟
(1) 初始化可行標桿?
(2) 用匈牙利算法尋找完備匹配?
(3) 若未找到完備匹配則修改可行標桿?
(4) 重復(2)(3)直到找到相等子圖的完備匹配?
相關概念
◎KM算法是用于尋找帶權二分圖最佳匹配的算法。?
◎二分圖是這樣一種圖:所有頂點可以分成兩個集:X和Y,其中X和Y中的任意兩個在同一個集中的點都不相連,而來自X集的頂點與來自Y集的頂點有連線。當這些連線被賦于一定的權重時,這樣的二分圖便是帶權二分圖。?
◎二分圖匹配是指求出一組邊,其中的頂點分別在兩個集合中,且任意兩條邊都沒有相同的頂點,這組邊叫做二分圖的匹配,而所能得到的最大的邊的個數,叫做二分圖的最大匹配。?
<Tip> 我們也可以換個角度看二分圖的最大匹配,即二分圖的每條邊的默認權重為1,我們求到的二分圖的最大匹配的權重最大。對于帶權二分圖,其邊有大于0的權重,找到一組匹配,使其權重最大,即為帶權二分圖的最佳匹配。?
匈牙利算法
匈牙利算法一般用于尋找二分圖的最大匹配。算法根據一定的規則選擇二分圖的邊加入匹配子圖中,其基本模式為:?
◎初始化匹配子圖為空?
◎while 找得到增廣路徑?
? ?do 把增廣路徑添加到匹配子圖中?
增廣路徑
增廣路徑的特性
◎有奇數條邊?
◎起點在二分圖的X邊,終點在二分圖的Y邊?
◎?路徑上的點一定是一個在X邊,一個在Y邊,交錯出現
◎整條路徑上沒有重復的點?
◎?起點和終點都是目前還沒有配對的點,其他的點都已經出現在匹配子圖中?
◎路徑上的所有第奇數條邊都是目前還沒有進入目前的匹配子圖的邊,而所有第偶數條邊都已經進入目前的匹配子圖。奇數邊比偶數邊多一條邊?
◎于是當我們把所有第奇數條邊都加到匹配子圖并把條偶數條邊都刪除,匹配數增加了1
實現過程
例如下圖,藍色的是當前的匹配子圖,目前只有邊x0y0,然后通過x1找到了增廣路徑:x1y0->y0x0->x0y2?
?
其中第奇數第邊x1y0和x0y2不在當前的匹配子圖中,而第偶數條邊x0y0在匹配子圖中,通過添加x1y0和x0y2到匹配子圖并刪除x0y0,使得匹配數由1增加到了2。每找到一條增廣路徑,通過添加刪除邊,我們總是能使匹配數加1.?
增廣路徑有兩種尋徑方法,一個是DFS,一個是BFS。例如從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算法的最大特點在于利用標桿和權重來生成一個二分子圖,在該二分子圖上面找最大匹配,而且,當些僅當找到完備匹配,才能得到最佳匹配。標桿和權重的作用在于限制新邊的加入,使得加入的新邊總是能為子圖添加匹配數,同時又令權重和得到最大的提高。
實現代碼
/*HDU-2255*/ #include<cstdio> #include<algorithm> #include<cstring> #include<iostream>#define MAXN 310 #define INF 0x3f3f3f3fusing namespace std;int nx,ny; int g[MAXN][MAXN]; int linker[MAXN],linkx[MAXN],linky[MAXN]; int slack[MAXN]; bool visx[MAXN],visy[MAXN];bool dfs(int k) {visx[k]=true;for(int y=0;y<ny;y++){if(visy[y])continue;int tmp=linkx[k]+linky[y]-g[k][y];if(!tmp){visy[y]=true;if(linker[y]==-1||dfs(linker[y])){linker[y]=k;return true;}}else if(slack[y]>tmp)slack[y]=tmp;}return false; } int KM() {memset(linker,-1,sizeof(linker));memset(linky,0,sizeof(linky));memset(linkx,-INF,sizeof(linkx));for(int i=0;i<nx;i++){for(int j=0;j<ny;j++){if(g[i][j]>linkx[i])linkx[i]=g[i][j];}}for(int x=0;x<nx;x++){for(int i=0;i<ny;i++)slack[i]=INF;while(true){memset(visx,false,sizeof(visx));memset(visy,false,sizeof(visy));if(dfs(x)==true)break;int d=INF;for(int i=0;i<ny;i++)if(!visy[i]&&d>slack[i])d=slack[i];for(int i=0;i<nx;i++){if(visx[i])linkx[i]-=d;}for(int i=0;i<ny;i++){if(visy[i])linky[i]+=d;elseslack[i]-=d;}}}int res=0;for(int i=0;i<ny;i++)if(linker[i]!=-1)res+=g[linker[i]][i];return res; }int main() {int n;while(scanf("%d",&n)!=EOF){for(int i=0;i<n;i++)for(int j=0;j<n;j++)scanf("%d",&g[i][j]);nx=ny=n;printf("%d\n",KM());}return 0; }?
Time: 2017-07-05
轉載于:https://www.cnblogs.com/SinGuLaRiTy2001/p/7123056.html
總結
以上是生活随笔為你收集整理的[SinGuLaRiTy] KM算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS 更改项目名称
- 下一篇: 利用记录型信号量机制: wait(s),