這個是在工做中遇到的一個實際的算法問題,問題描述以下,當前有m個司機,n個乘客,每一個司機和每一個乘客的距離由經緯度能夠計算獲得,如何匹配可使其去接乘客的距離和最小?(只能一個司機接一個乘客)算法
帶權二分圖方法
通常對KM算法的描述,基本上能夠歸納成如下幾個步驟:ide
(1) 初始化可行標杆
(2) 用匈牙利算法尋找完備匹配
(3) 若未找到完備匹配則修改可行標杆
(4) 重複(2)(3)直到找到相等子圖的完備匹配spa
關於該算法的流程及實施,網上有不少介紹,基本上都是圍繞可行標杆如何修改而進行的討論,至於原理並無給出深刻的探討。3d
KM算法是用於尋找帶權二分圖最佳匹配的算法。code
二分圖是這樣一種圖:全部頂點能夠分紅兩個集:X和Y,其中X和Y中的任意兩個在同一個集中的點都不相連,而來自X集的頂點與來自Y集的頂點有連線。當這些連線被賦於必定的權重時,這樣的二分圖即是帶權二分圖。blog
二分圖匹配是指求出一組邊,其中的頂點分別在兩個集合中,且任意兩條邊都沒有相同的頂點,這組邊叫作二分圖的匹配,而所能獲得的最大的邊的個數,叫作二分圖的最大匹配。遞歸
咱們也能夠換個角度看二分圖的最大匹配,即二分圖的每條邊的默認權重爲1,咱們求到的二分圖的最大匹配的權重最大。對於帶權二分圖,其邊有大於0的權重,找到一組匹配,使其權重最大,即爲帶權二分圖的最佳匹配。隊列
匈牙利算法通常用於尋找二分圖的最大匹配。算法根據必定的規則選擇二分圖的邊加入匹配子圖中,其基本模式爲:數學
初始化匹配子圖爲空
While 找獲得增廣路徑
Do 把增廣路徑添加到匹配子圖中string
增廣路徑有以下特性:
例以下圖,藍色的是當前的匹配子圖,目前只有邊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算法的最大特色在於利用標杆和權重來生成一個二分子圖,在該二分子圖上面找最大匹配,並且,當些僅當找到完備匹配,才能獲得最佳匹配。標杆和權重的做用在於限制新邊的加入,使得加入的新邊老是能爲子圖添加匹配數,同時又令權重和獲得最大的提升。
#include <cstdio> #include <string.h> #include <vector> #include <algorithm> using namespace std; int const MAX = 1000; int const inf = 0x3fffffff; int w[MAX][MAX]; int link[MAX];//表明當前與Y集合中配對的X集合中的點 int visx[MAX], visy[MAX]; int lx[MAX], ly[MAX]; int n, m;//表明X和Y中元素的個數 int can(int t) { visx[t] = 1; for(int i = 1; i <= m; i++){ if(!visy[i] && lx[t] + ly[i] == w[t][i]){//這裏「lx[t]+ly[i]==w[t][i]」決定了這是在相等子圖中找增廣路的前提,很是重要 visy[i] = 1; if(link[i] == -1 || can(link[i])){ link[i] = t; return 1; } } } return 0; } int km() { int sum = 0; memset(ly, 0, sizeof(ly)); for(int i = 1; i <= n; i++){//把各個lx的值都設爲當前w[i][j]的最大值 lx[i] = -inf; for(int j = 1; j <= n; j++){ if(lx[i] < w[i][j]) lx[i] = w[i][j]; } } memset(link, -1, sizeof(link)); for(int i = 1; i <= n; i++){ while(1){ memset(visx, 0, sizeof(visx)); memset(visy, 0, sizeof(visy)); if(can(i))//若是它可以造成一條增廣路徑,那麼就break break; int d = inf;//不然,後面應該加入新的邊,這裏應該先計算d值 for(int j = 1; j <= n; j++)//對於搜索過的路徑上的XY點,設該路徑上的X頂點集爲S,Y頂點集爲T,對全部在S中的點xi及不在T中的點yj if(visx[j]) for(int k = 1; k <= m; k++) if(!visy[k]) d = min(d, lx[j] + ly[k] - w[j][k]); if(d == inf) return -1;//找不到能夠加入的邊,返回失敗(即找不到完美匹配) for (int j = 1; j <= n; j++) if (visx[j]) lx[j] -= d; for(int j = 1; j <= m; j++) if(visy[j]) ly[j] += d; } } for(int i = 1; i <= m; i++) if(link[i] > -1) sum += w[link[i]][i]; return sum; }
這個地方注意 咱們是求最小值 只須要把上面的換個符號便可 上面的算法是求最大值