我第一次理解KM算法看到大神的講解不勝感激這km挺神奇的接下來就見識一下這個大牛的吧 php
轉自 http://blog.csdn.net/wuxinxiaohuangdou/article/details/14056987ios
以杭電的2255題爲例來說解KM算法
對於這裏給出 一:基本概念 二:算法原理和語言描述 三:結合圖形理解KM算法過程數組
一.函數
首先給出一些摘要知識點以及算法的語言描述(若是前面看過前輩們的,只是對於算法過程不瞭解的能夠直接看後面結合圖形的算法詳細解說,這裏概括我的以爲對於一個小白學習有用的知識點)學習
首先spa
1. 完備匹配:以下圖,設G=<V1,V2,E>爲二部圖,|V1|≤|V2|,M爲G中一個最大匹配,且|M|=|V1|,則稱M爲V1到V2的完備匹配。.net
那麼對於V1的點都有與之對應的匹配,就是完備匹配(就是徹底把V1的點都匹配完)code
2. 可行頂標:對於左邊的點設 lx[MAX] 數組, 對於右邊的點設 ly[MAX] 數組,Vi,j 表示 vi 到 vj 的權值(這裏的緣由是爲了求解最優完備匹配,能夠在下面理解)htm
3. 相等子圖:相等子圖爲完備匹配中全部的匹配(就是所有的V1的點,和與之匹配的V2中的點),可是對於邊就只包含lx[i] + ly[j] = V(i,j)的邊
4.最優完備匹配:那麼這個就是咱們的目的啦,對於上面說的完備匹配之下要求最優,那麼結合相等子圖來就有若由二分圖中全部知足A[i]+B[j]=w[i,j]的邊(i,j)構成的相等子圖有完備匹配,那麼這個完備匹配就是二分圖的最大權匹配。
這個定理是顯然的。由於對於二分圖的任意一個匹配,若是它包含於相等子圖,那麼它的邊權和等於全部頂點的頂標和;若是它有的邊不包含於相等子圖,那麼它的邊權和小於全部頂點的頂標和。因此相等子圖的完備匹配必定是二分圖的最大權匹配。
5. 交錯樹:這裏是在對V1中的一個頂點進行匹配的時候所標記過得V一、V2的點和與之的連線,造成的一個像樹裝的圖
二:
那麼在瞭解了以上的基本概念以後,就來接觸KM算法
1.首先基本的原理:該算法是經過給每一個頂點一個標號(叫作頂標)來把求最大權匹配的問題轉化爲求完備匹配的問題的。設頂點V1的頂標爲lx[ i ],V2頂點的頂標爲ly[ j ],頂點V1的 i 與V2的 j 之間的邊權爲V[i,j]。在算法執行過程當中的任一時刻,對於任一條邊(i,j),lx[ i ]+ly[j]>=V[i,j]始終成立。
2.基本流程:
(1).初始時爲了使lx[ i ]+ly[j]>=V[i,j]恆成立,將V1的點的標號記爲與其相連邊的最大邊權值,V2的點標號全記爲0
(2)用匈牙利算法在相等子圖尋找完備匹配
(3)若未找到完備匹配則修改可行頂標的值 ,擴充相等子圖
(4)重複(2)(3)直到找到相等子圖的完備匹配爲止
3.這裏值得注意的是找完備匹配不難理解,主要是進行可行頂標的修改擴充相等子圖,有:
引用:樸素的實現方法,時間複雜度爲O(n4)——須要找O(n)次增廣路, 每次增廣最多須要修改O(n)次頂 標,每次修改頂標時因爲要枚舉邊來求d值,複雜度爲O(n2)。實際上KM算法的複雜度是能夠作到O(n3)的。咱們給每一個Y頂點一個「鬆弛量」函數 slack,每次開始找增廣路時初始化爲無窮大。在尋找增廣路的過程當中,檢查邊(i,j)時,若是它不在相等子圖中,則讓slack[j]變成原值與A [i]+B[j]-w[i,j]的較小值。這樣,在修改頂標時,取全部不在交錯樹中的Y頂點的slack值中的最小值做爲d值便可。但還要注意一點:修改 頂標後,要把全部的slack值都減去d。
-----------------------------------------------------------------------------------------------------------------------
三:那麼這裏咱們 就結合圖形來理解擴充相等子圖以及KM的算法過程
(能夠結合模板代碼看)
這裏結合hdu2255:若是對應有案例
3
2 1 1
3 2 1
1 1 1
首先給出如下圖:(V1,V2,V[i,j])v[i,j]表示邊的權值
那麼首先就是進行V1中的1的匹配,得下圖 (INF表示無窮大)
用紅色的邊表明匹配,這裏對於V1的1能順利一次匹配成功,就沒有更改vis 的一些值,(小小的偷懶),那麼下面就要對V1的2號頂點進行匹配,那麼此過程當中咱們,咱們首先考慮V2的1號點,發現它被V1的1號匹配了,那麼就先把V1的1號趕走去和V2其餘的幾個點嘗試匹配,而且在這個過程當中咱們會修改上圖以下:
對於這一次匹配的過程當中咱們發現造成了一個上面說的交錯樹,那麼對於V1的2號匹配失敗以後咱們就開始了修改頂標並擴充相等子圖的操做,(注意此過程咱們找出了 lx[i]+ly[j]-V[i,j] 的最小存於slack中,咱們擴充的過程當中找一次找到最小的便可)
那麼首先咱們要找不在交叉樹中的V2的 j 點中slack[j]最小的 爲 d=1
那麼修改以後再一次匹配以前以下圖:
那麼咱們發現lx[1]+ly[1]-V[1,1]沒有變,lx[2]+ly[1]-V[2,1]也沒有變,可是咱們能夠發現其餘的lx[1]與lx[2]與其餘的ly的和減少啦,那麼V1的2號再次匹配的過程的時候就在考慮V2的1號的時候,發現V2的1號匹配者V1的1號能夠去匹配V2的2號,這裏就是擴充了一條邊,那麼這裏就匹配成以下圖:
那麼下面還有一個V1的頂點3,這個看起來可能第一感受會說,直接匹配V2的3就是的,可是實際上KM的算法過程不是那麼簡單的,那麼咱們來一步步的模擬,首先,咱們V1的3號去嘗試匹配V2的1號,發現lx[3]+ly[1]-V[3,1]=1,那麼匹配失敗,更新以下圖:
那麼V1的3號就再考慮V2的2,此時有lx[3]+ly[2]-V[3,2]=0,可是發現V2的2早被V1的1匹配了(link[2]=1),那麼根據匈牙利算法,把V1的1趕走去嘗試與V2的其餘點匹配,那麼此時咱們先關注V1的1,如今它能夠考慮V2的1號,發現lx[1]+ly[1]-V[1,1]=0,可是此時V2的1號被V1的2號匹配啦(link[1]=2),那麼一樣根據匈牙利算法,把V1的2號趕走,那麼此時以下圖:(注意紅的色部分的值變化)
那麼在此圖的基礎上,咱們把關注V1的2號,發現只有V2的3號沒有訪問過,那麼嘗試匹配發現lx[2]+ly[3]-V[2,3]=1,匹配失敗,那麼V1的2號無處可去,回溯到被趕走的V1的1號,它發現趕不走V2的1的匹配者,那麼就本身再找V2中別的人匹配,發現V2的2號被訪問過啦,那麼就找V2的3,此時發現能匹配,那麼本身立刻佔領這裏,那麼此時就再回溯到V1的3,由於它把佔領V2-2號的人趕到V2-1去了,因此本身就佔領這裏,那麼以下圖:
//421MS 616K 1711 B #include <iostream> #include<cstdio> #include<cstring> using namespace std; #define MAX 310 #define INF 1<<25 #define clr(x) memset(x,0,sizeof(x)) /**注:發生矛盾,即幾個居民同搶一個房子**/ int w[MAX][MAX]; int n; int lx[MAX],ly[MAX]; int link[MAX]; int slack[MAX]; int visx[MAX],visy[MAX]; bool dfs(int x) { visx[x]=1; /****獲得發生矛盾的居民集合****/ for(int y=1;y<=n;y++) /**這個居民,每一個房子都去試一試!(找到就退出)**/ { if(visy[y]) /****一個房子不須要重複訪問****/ continue; int t=lx[x]+ly[y]-w[x][y];/****按這個標準去用-匈牙利算法***/ if(t==0)/**t==0標誌這個房子能夠給這位居民**/ { visy[y]=1; if(link[y]==0||dfs(link[y])) /****這房子沒人住 或 可讓住這個房子的人去找另外的房子住****/ { link[y]=x; return true;/**那麼就可讓這位居民住進來**/ } } else if(slack[y]>t)/**不然這個房子不能給這位居民!**/ slack[y]=t;/***就要找到這個房子要鬆弛多少纔可以給這位居民***/ /***且當有多個居民都對這個房子有鬆弛量時,要找到最小的。****/ } return false; } int KM() { clr(lx); clr(ly); clr(link); /*****首先把每一個居民出的錢最多的那個房子賦給它******/ for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(lx[i]<w[i][j]) lx[i]=w[i][j]; /*****n循環-在知足了之前居民的狀況下-給第i個居民安置房子*****/ for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) slack[j]=INF; /***鬆弛量***//****這個鬆弛量不須要每次dfs都初始化一次;由於它跟visy有關***/ while(1)/****死循環-在於必定要給這個居民找到房子爲止!****/ { clr(visx); clr(visy); if(dfs(i)) /***找到房子就跳出死循環***/ break; int d=INF; for(int k=1;k<=n;k++) if(!visy[k]&&d>slack[k]) d=slack[k]; /****找到最小松弛量*****/ for(int k=1;k<=n;k++)/****鬆弛操做-使發生矛盾的居民有更多選擇*****/ { if(visx[k]) /*****將矛盾居民的要求下降,使他們有更多可房子選擇*****/ lx[k]-=d; if(visy[k]) /****使發生矛盾的房子在下一個子圖,保持矛盾(即保持原子圖性質)*****/ ly[k]+=d; } } } int ans=0; for(int i=1;i<=n;i++) ans+=w[link[i]][i]; return ans; } int main() { while(~scanf("%d",&n)) { clr(w);//每一個案列都要從新置0; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&w[i][j]);//輸入每邊權值 printf("%d\n",KM()); } return 0; }嘎嘎應該懂了纔對