司機乘客匹配中的距離和最小問題

這個是在工做中遇到的一個實際的算法問題,問題描述以下,當前有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

增廣路徑有以下特性:

  1. 有奇數條邊
  2. 起點在二分圖的X邊,終點在二分圖的Y邊
  3. 路徑上的點必定是一個在X邊,一個在Y邊,交錯出現。
  4. 整條路徑上沒有重複的點
  5. 起點和終點都是目前尚未配對的點,其餘的點都已經出如今匹配子圖中
  6. 路徑上的全部第奇數條邊都是目前尚未進入目前的匹配子圖的邊,而全部第偶數條邊都已經進入目前的匹配子圖。奇數邊比偶數邊多一條邊
  7. 因而當咱們把全部第奇數條邊都加到匹配子圖並把條偶數條邊都刪除,匹配數增長了1.

例以下圖,藍色的是當前的匹配子圖,目前只有邊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;  
}

這個地方注意 咱們是求最小值 只須要把上面的換個符號便可 上面的算法是求最大值

相關文章
相關標籤/搜索