二分图最优匹配「KM算法」
KM算法用來求二分圖最大權(quán)完美匹配
一般對(duì)KM算法的描述,基本上可以概括成以下幾個(gè)步驟:
(1) 初始化可行標(biāo)桿
(2) 用匈牙利算法尋找完備匹配
(3) 若未找到完備匹配則修改可行標(biāo)桿
(4) 重復(fù)(2)(3)直到找到相等子圖的完備匹配
二分圖匹配里面我們找最大邊進(jìn)行連邊!
但是遇到某個(gè)點(diǎn)被匹配了兩次怎么辦?
那就用匈牙利算法進(jìn)行更改匹配!
這就是KM算法的思路了:盡量找最大的邊進(jìn)行連邊,如果不能則換一條較大的。引用鏈接
添加標(biāo)桿(頂標(biāo))是怎樣子呢?我們對(duì)左邊每個(gè)點(diǎn)Xi和右邊每個(gè)點(diǎn)Yi添加標(biāo)桿Cx和Cy。
其中我們要滿足Cx+Cy>=w[x][y](w[x][y]即為點(diǎn)Xi、Yi之間的邊權(quán)值)
對(duì)于一開始的初始化,我們對(duì)于每個(gè)點(diǎn)分別進(jìn)行如下操作
Cx=max(w[x][y]);
Cy=0;
然后,我們可以進(jìn)行連邊,即采用匈牙利算法,只是在判斷兩點(diǎn)之間是否有連線的條件下,因?yàn)槲覀円獙⒆畲筮呥M(jìn)行連線,所以原來判斷是否有邊的條件w[x][y]==0換成了
Cx+Cy==w[x][y]
此時(shí),有一個(gè)新的名詞——相等子圖。
因?yàn)槲覀兺ㄟ^了巧妙的處理讓計(jì)算機(jī)自動(dòng)連接邊權(quán)最大的邊,換句話說,其他邊計(jì)算機(jī)就不會(huì)連了,也就“不存在”這個(gè)圖中,但我們可以隨時(shí)加上這些“不存在”圖中的邊。此時(shí)這個(gè)圖可以認(rèn)為是原圖的子圖,并且是等效。
這樣,計(jì)算機(jī)在枚舉右邊的點(diǎn)的時(shí)候,滿足以上條件,就能夠知道這條邊是我們要連的最大的邊,就能進(jìn)行連邊了。
于是乎我們連了AD。
接下來就尷尬了,計(jì)算機(jī)接下來要連B點(diǎn)的BD,但是D點(diǎn)已經(jīng)和A點(diǎn)連了,怎么辦呢???
根據(jù)匈牙利算法,我們做的是將A點(diǎn)與其他點(diǎn)進(jìn)行連線,但此時(shí)的子圖里“不存在”與A點(diǎn)相連的其他邊,怎么辦呢??
為此,我們就需要加上這些邊!
很明顯,我們添邊,自然要加上不在子圖中邊權(quán)最大的邊,也就是和子圖里這個(gè)邊權(quán)值差最小的邊。
于是,我們?cè)僖欢纫肓艘蛔兞縟,d=min{Cx[i]+Cy[j]-w[i][j]}
其中,在這個(gè)題目里Cx[i]指的是A的標(biāo)桿,Cy[j]是除D點(diǎn)(即已連點(diǎn))以外的點(diǎn)的標(biāo)桿。
隨后,對(duì)于原先存在于子圖的邊AD,我們將A的標(biāo)桿Cx[i]減去d,D的標(biāo)桿Cy[d]加上d。
這樣,這就保證了原先存在AD邊保留在了子圖中,并且把不在子圖的最大權(quán)值的與A點(diǎn)相連的邊AE添加到了子圖。
因?yàn)橛?jì)算機(jī)判斷一條邊是否在該子圖的條件是其兩端的頂點(diǎn)的標(biāo)桿滿足
Cx+Cy==w[x][y]
對(duì)于原先的邊,我們對(duì)左端點(diǎn)的標(biāo)桿減去了d,對(duì)右端點(diǎn)的標(biāo)桿加上了d,所以最終的結(jié)果還是不變,仍然是w[x][y]。
對(duì)于我們要添加的邊,我們對(duì)于左端點(diǎn)減去了d,即Cx[i]=Cx[i]-d;為方便表示我們把更改后的的Cx[i]視為Cz[i],即Cz[i]=Cx[i]-d;
對(duì)于右端點(diǎn),我們并沒有對(duì)其進(jìn)行操作。那這條我們要添加邊的兩端點(diǎn)的標(biāo)號(hào)是否滿足Cz[i]+Cy[j]=w[i][j]?
因?yàn)?/strong>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]);
化簡(jiǎn)得Cz[i]+Cy[j]=w[i][j]。
------滿足了要求!即添加了新的邊。--------
值得注意的是,這里我們只是對(duì)于一條邊操作,當(dāng)我們添加了幾條邊,要進(jìn)行如上操作時(shí),要保證原先存在的邊不消失,那么我們就要先求出了d,然后
對(duì)于每個(gè)連邊的左端點(diǎn)(記作集合S)的每個(gè)點(diǎn)的標(biāo)號(hào)減去了d之后,然后連邊的右端點(diǎn)(記作T)加上d,這樣就保證了原先的邊不消失啦~
實(shí)際上這就是一直在尋找著增廣路,通過不斷修改標(biāo)桿進(jìn)行添邊實(shí)現(xiàn)。
接下來就繼續(xù)著匈牙利算法,直到完全匹配完為止。
該算法的正確性就在于 它每次都選擇最大的邊進(jìn)行連邊
至此,我們?cè)倩仡橩M算法的步驟:
*1.用鄰接矩陣(或其他方法也行啦)來儲(chǔ)存圖。*
2.運(yùn)用貪心算法初始化標(biāo)桿。
*3.運(yùn)用匈牙利算法找到完備匹配。*
*4.如果找不到,則通過修改標(biāo)桿,增加一些邊。*
5.重復(fù)3,4的步驟,直到完全匹配時(shí)可結(jié)束。
引用鏈接
現(xiàn)在有N男N女,有些男生和女生之間互相有好感,我們將其好感程度定義為好感度,我們希望把他們兩兩配對(duì),并且最后希望好感度和最大。
怎么選擇最優(yōu)的配對(duì)方法呢?
首先,每個(gè)女生會(huì)有一個(gè)期望值,就是與她有好感度的男生中最大的好感度。男生呢,期望值為0,就是……只要有一個(gè)妹子就可以啦,不挑~~
這樣,我們把每個(gè)人的期望值標(biāo)出來。
接下來,開始配對(duì)。
配對(duì)方法:
我們從第一個(gè)女生開始,分別為每一個(gè)女生找對(duì)象。
每次都從第一個(gè)男生開始,選擇一個(gè)男生,使男女兩人的期望和要等于兩人之間的好感度。
注意:每一輪匹配,每個(gè)男生只會(huì)被嘗試匹配一次!
具體匹配過程:
為女1找對(duì)象=
(此時(shí)無人配對(duì)成功)
根據(jù) “男女兩人的期望和要等于兩人之間的好感度”的規(guī)則
女1-男1:4+0 != 3
女1-男3:4+0 == 4
所以女1選擇了男3
女1找對(duì)象成功
==為女1找對(duì)象成功
為女2找對(duì)象=
(此時(shí)女1—男3)
根據(jù)配對(duì)原則,女2選擇男3
男3有主女1,女1嘗試換人
我們嘗試讓女1去找別人
嘗試失敗
為女2找對(duì)象失?。?/p>
==為女2找對(duì)象失敗
這一輪參與匹配的人有:女1,女2,男3。
怎么辦???很容易想到的,這兩個(gè)女生只能降低一下期望值了,降低多少呢?
任意一個(gè)參與匹配女生能換到任意一個(gè)這輪沒有被選擇過的男生所需要降低的最小值(就可以保證每個(gè)女的都可以匹配到一個(gè)男的)
比如:女1選擇男1,期望值要降低1。 女2選擇男1,期望值要降低1。 女2選擇男2,期望值要降低2。
于是,只要期望值降低1,就有妹子可能選擇其他人。所以妹子們的期望值要降低1點(diǎn)。
同時(shí),剛才被搶的男生此時(shí)非常得意,因?yàn)橛忻米觼頁(yè)屗谑撬钠谕堤岣吡?點(diǎn)(就是同妹子們降低的期望值相同)。
于是期望值變成這樣(當(dāng)然,不參與剛才匹配過程的人期望值不變)
==繼續(xù)為女2找對(duì)象=
(此時(shí)女1—男3)
女2選擇了男1
男1還沒有被配對(duì)
女2找對(duì)象成功!
==為女2找對(duì)象成功=
為女3找對(duì)象=
(此時(shí)女1—男3,女2-男1)
女3沒有可以配對(duì)的男生……
女3找對(duì)象失敗
==為女3找對(duì)象失敗
此輪只有女3參與匹配
此時(shí)應(yīng)該為女3降低期望值
降低期望值1的時(shí)候,女3-男3可以配對(duì),所以女3降低期望值1
==繼續(xù)為女3找對(duì)象
(此時(shí)女1—男3, 女2-男1)
女3相中了男3
此時(shí)男3已經(jīng)有主女1,于是女1嘗試換人
女1選擇男1
而男1也已經(jīng)有主女2,女2嘗試換人
前面說過,每一輪匹配每個(gè)男生只被匹配一次
所以女2換人失敗
女3找對(duì)象再次失敗
==為女3找對(duì)象失敗
這一輪匹配相關(guān)人員:女1,女2,女3,男1,男3
此時(shí),只要女2降低1點(diǎn)期望值,就能換到男2
(前面提過 只要任意一個(gè)女生能換到任意一個(gè)沒有被選擇過的男生所需要降低的最小值)
我們把相應(yīng)人員期望值改變一下
==還是為女3找對(duì)象
(此時(shí)女1—男3, 女2-男1)
女3選擇了男3
男3有主女1,女1嘗試換人
女1換到了男1
男1已經(jīng)有主女2,女2嘗試換人
女2換人男2
男2無主,匹配成功!!!
==為女3找對(duì)象成功=
匹配成功?。?!撒花~~
到此匹配全部結(jié)束
此時(shí)
女1-男1,女2-男2,女3-男3
好感度和為最大:9
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
int love[310][310];//每個(gè)女生對(duì)男生的好感度(W兩點(diǎn)之間的權(quán)值)
int exboy[310];//男生的吸引值(右標(biāo)桿)
int exgirl[310];//女生的期望值(左標(biāo)桿)
int visboy[310];//標(biāo)記是否在此次匹配中出現(xiàn)
int visgirl[310];//標(biāo)記是否在此次匹配中出現(xiàn)
int match[310];//標(biāo)記每個(gè)男生匹配到的女生編號(hào),沒有匹配到則為-1
int slack[310];//記錄每個(gè)男生想被女生匹配到最小需要多少吸引值
int n;
int Hungarian(int girl)
{
    visgirl[girl]=1;//標(biāo)記這個(gè)女生已經(jīng)在匹配中出現(xiàn)
    for(int boy=1; boy<=n; boy++)
    {
        if(visboy[boy])//男生已經(jīng)被匹配,就繼續(xù)下一個(gè)男生
        {
            continue;
        }
        int tep=exboy[boy]+exgirl[girl]-love[girl][boy];
        if(tep==0)//男生的吸引值加上女生的期望值==女生對(duì)男生的好感度(左標(biāo)桿+右標(biāo)桿==左標(biāo)桿節(jié)點(diǎn)到右標(biāo)桿節(jié)點(diǎn)的權(quán)值)
        {
            visboy[boy]=1;
            if(match[boy]==-1||Hungarian(match[boy]))
            {
                match[boy]=girl;//標(biāo)記這個(gè)男生匹配到的女生編號(hào)
                return true;
            }
        }
        else//意味著男生的吸引值不夠,記錄每個(gè)男生需要匹配到女生的最小吸引值
        {
            slack[boy]=min(slack[boy],tep);
        }
    }
    return false;
}
void KM( )
{
    memset(match,-1,sizeof(match));
    memset(exboy,0,sizeof(exboy));//開始每個(gè)男生都沒有女生與他匹配,吸引值為0
    for(int i=1; i<=n; i++)
    {
        exgirl[i]=love[i][1];
        for(int j=2; j<=n; j++)
        {
            exgirl[i]=max(exgirl[i],love[i][j]);//初始化每個(gè)女生的期望值都是最大的(初始化左標(biāo)桿)
        }
    }
    // cout<<"flag"<<endl;
    // for(int i=1;i<=n;i++)
    // {
    //     cout<<exgirl[i]<<" "<<exboy[i]<<endl;
    // }
    for(int i=1; i<=n; i++)//每一個(gè)女生開始進(jìn)行匹配
    {
        memset(slack,INF,sizeof(slack));//尋找最小的吸引值,所以初始化為正無窮
        while(1)
        {
            memset(visgirl,0,sizeof(visgirl));
            memset(visboy,0,sizeof(visboy));
            if(Hungarian(i))//找到匹配策略,跳出循環(huán)
            {
                break;
            }
//沒有找到匹配策略,于是更新女生的期望值(左標(biāo)桿),男生的吸引值(右標(biāo)桿)
            int d=INF;
            for(int j=1; j<=n; j++)
            {
                if(!visboy[j])
                {
                    d=min(d,slack[j]);//找出最小的吸引值(那個(gè)男生就可以匹配到女生啦)
                }
            }
            for(int j=1; j<=n; j++)
            {
                if(visgirl[j])//在匹配中出現(xiàn)的女生為了妥協(xié),期望值下降d(心里不甘心下降,于是盡可能的下降少一點(diǎn)期望值)
                {
                    exgirl[j]-=d;
                }
                if(visboy[j])//在匹配中出現(xiàn)的男生由于有女生搶他,所以吸引值上升d(上升的值完全取決于女生想下降多少,于是就上升了最小的d)
                {
                    exboy[j]+=d;
                }
                else//沒在匹配中的男生,由于女生的期望值降低,所以他需要的吸引值也下降d(不用那么辛苦訓(xùn)練吸引值了)
                {
                    slack[j]-=d;
                }
            }
        }
    }
    int res=0;
    for(int i=1; i<=n; i++)
    {
        res+=love[match[i]][i];//第i個(gè)男生匹配到的女生編號(hào)是match[i]
    }
    printf("%d
",res);
}
int main( )
{
    while(~scanf("%d",&n))
    {
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=n; j++)
            {
                scanf("%d",&love[i][j]);
            }
        }
        KM( );
    }
    return 0;
}
總結(jié)
以上是生活随笔為你收集整理的二分图最优匹配「KM算法」的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Cookie test
- 下一篇: SAP WebClient UI vie
