菜鳥談談二分圖的最佳匹配


    參考《啊哈算法》 

 

    有一種很特別的圖,就做二分圖,那什么是二分圖呢?就是能分成兩組,S,T。其中,S上的點不能相互連通,只能連去T中的點,同理,T中的點不能相互連通,只能連去S中的點。這樣,就叫做二分圖。

舉個很通俗的例子,現在有三個男生和三個女生,要組隊一起去報名旅游(情侶報名可以半價哦!),所以,他們就想盡辦法湊成一對,就算不是情侶,都說是啦,為了減錢,更可況,我們這些單身狗就是靠這些機會脫單的, ̄□ ̄||。……..為什么可以看成二分圖呢?你想啊,男和男在一對,搞基啊?所以,同性的我們規定不能在一起。那么現在問題來了,我現在想知道它們6個人,能組成多少對?這就引入了一個新的問題:二分圖的最【大】匹配。顧名思義,就是求二分圖中能組成的最大匹配數,比如那6個最多能組成3對,但是能不能組成還是要看條件的。因為小時候爺爺教過我們,不能隨便和陌生人打交道嘛,所以我們這里規定,它們只有在認識(就是圖能連通) 的情況下,才能組成一對。

現在有男孩:A、B、C

女孩:X、Y、Z

下面給出他們的關系,看清楚那。A認識X、Y

B認識Y、Z   由於C比較內向,只認識X

好啦,現在就要求他們最多能組成多少對啦!唉,為了減錢,費點腦力,值!其實一看就知道了:3對。A—Y  B—Z  C—X  。但是,我們沒辦法一下子就得出這個結論的,為什么呢?因為A B C本來互不認識,A也不知道C是不是認識X,所以,A去搭配X或者Y對於A來說是一樣的,為什么一定要A搭配Y?所以,我們就算他們不認識,我們也要用算法解他們出來,這就是我們的二分圖的最大匹配。現在我們來模擬一下整個過程?首先,A來到X小姐面前,禮貌地問:Could I travel with you?  X小姐想都沒想,立馬答應了(這里我們假設女的一定要答應任何一個男的,為什么?無解,因為我們只是求他們搭配的最大對數,其他的都是次要的,但是我們后面說的最佳匹配,那就不同了)。然后到B先生了,首先,他來到Y中,和Y組合了一起。現在輪到C先生了,C只認識X,然后他去到X小姐身邊,說:I  wanner ……遺憾的是:X小姐說:我已經和別人組成一隊了啊?(怎么辦?C先生顏值好像比較高,好想和他一起組隊啊……)咦?“你等等,我去問問A先生能不能和別人一起組隊先。”X小姐說。然后A先生就只能跑去Y小姐那里了(因為A先生也認識Y小姐),禮貌地問:Could I travel with you? 哇塞!Y小姐最喜歡會說英語的男人了,但是我已經有隊伍了啊。咦?你們猜到了吧,如法炮制。對A說:你等等,我問問B能不能和別人組隊。然后到B了,他去問Z,我們….能在一起….組隊嗎?沒人搭理的Z小姐當然願意啦,就說:“我正好空着呢!我們一起吧!”。然后,B就告訴Y,我找到別人啦,88。Y告訴A,我們可以一起去啦,yeah!!!(心想)。然后A告訴X,我找到人啦,886。然后X告訴C,我們可以一起去啦。 ̄□ ̄||…..弄了那么久,終於使匹配數增加一了,這里我們把它叫做增廣路。增廣路的作用就是“改進”匹配方案(也就是增加匹配對數),那么我們怎么確定他就是最大匹配呢?如果當前匹配方案下再也找不到增廣路,那就是最大匹配了,算法如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int n,m;//頂點數n和邊的數目m
int e[51][51];//保存一個無向圖
int book[51];//每次都標記那個去了和那個沒去
int match[51];//標記那個是匹配那個的
#define inf 9999//假設的無窮大
int dfs (int u)
{
    int i;
    for (i=1;i<=n;i++)
    {
        if (book[i]==0&&e[u][i]==1)//這個點在同一次上沒去過,而且他們能聯通
        {
            book[i]=1;//這個點已經嘗試過了
            if (match[i]==0||dfs(match[i]))//這個點還沒有被匹配,
                //或者,他可以匹配其他的人
            {
               //他們就能夠匹配
                match[i]=u;//判斷哪個,就是修改那個
        //match[u]=i;不用的,錯誤的
                return 1;
            }
        }
    }
    return 0;
}
void work ()
{
    scanf ("%d%d",&n,&m);//輸入頂點數n和邊的數目m
    int i;
    int j;
    for (i=1;i<=n;i++)
    {
        for (j=1;j<=n;j++)
        {
            if (i==j) e[i][j]=0;//初始化
            else      e[i][j]=inf;
        }
    }
    for (i=1;i<=m;i++)
    {
        int u,v;
        scanf ("%d%d",&u,&v);
        e[u][v]=1;
        e[v][u]=1;//無向圖
    }
    int i_count=0;
    for (i=1;i<=n;i++)//嘗試遍歷每一個頂點
    {
        memset (book,0,sizeof(book));
        if (dfs(i))
        {
            i_count++;//若能找到新的匹配,就++
        }
    }
    printf ("%d\n",i_count);
    return ;
}
int main ()
{
    work ();
    system ("pause");
    return 0;
}
View Code

遠處傳來一陣呼聲:“此路是我開,此樹是我栽,要想過此路,留低買路財!!!”————他們遇到山賊了!怎么辦怎么辦?對面雖然只有三個山賊,但是實力估計去到14點,我們雖說有六個人,但是,A搭配Y,力量有7點,B搭配Z,力量只有1點,(怎么那么少?嘿嘿,他們兩個都是文化生,不好動武嘛!),C搭配X,力量有5點, ̄□ ̄||  還差一點,難道這次旅游就倒在這里?是給錢還是怎樣呢?想來想去,A和C就說,B你為什么那么弱?只有1點?

B:…………  要是能高一點,也就好了,這樣吧,我們重新搭配吧,因為經過昨晚的狂歡,他們已經是互相認識了,另外,他們的搭配默契度有所不同哦,(默契度就是他們的力量),現在看看這個表格:

 

 

 

看來他們的默契度也不是很少,只是,B搭配Z太小了,這是她們打不過山賊的短板!現在我們要怎樣重組,才能打贏山賊呢?這就是我們的:二分圖的最【佳】匹配啦!顧名思義,就是在考慮能搭配的情況下,又要使得搭配后,整張圖邊的權值加起來是最大的,這樣的匹配,我們稱為最佳匹配。也叫KM算法

貪心選邊:何為貪心選邊?貪心,就是一個尋找局部最優解的過程,至於是不是全局最優解,需要證明才能用。就算不能每次選出最大值的邊,但是選出來的邊的總和都是前n個最優的。通俗點講,就是每次都選擇搭配最高的,就是現在的A,搭配Y是最高的,所以我們先把A—>Y。至於這樣會不會影響后面的呢?就好像BàY也是B中最高的,是5。這樣就會產生歧義,因為一個人不能同時匹配多個嘛。那么究竟是A-->Y,還是B-->Y呢?有人笑了,這不很簡單嗎?A-->Y是7,B-->Y是5,當然是A-->Y啊,比較大啊。那答案是不是這樣的呢?其實很容易證明他是不一定的,因為我們這里求的是全局最優解,不能因為個人匹配最大而否定別人,可以舉個極端的例子:假如:現在的B除了搭配Y是5外,他搭配X,Z都是比較小的,是-inf,就是負無窮,那么,當然要讓讓B啊,不然就不是全局最優解了。那么,我們怎么來確定誰選誰呢?

這里我們用兩個數組,也稱為標桿,fx[]和fy[]。

其中,開始的時候,fx[]的值為匹配的最大值,fy[]的值先初始化為0。

// fx[]={0,7,5,7},fy[]={0,0,0,0},

//前面一個fx[0]我沒用,同理fy

這里我們判斷兩人是否能匹配的時候,多加一個條件,就是fx[u]+fy[i]==e[u][i];//就是這兩個人匹配的值,要存在這個圖中。開始的時候相加是最大值。

我們先來看看KM算法的實現:

void do_km()
{
    int i,j;
    int d=inf;
    for (i=1;i<=n ;i++ )
    {
        if (vx[i]==1)
        {
            for (j=1;j<=n ;j++ )
            {
                if (vy[j]==0)
                {
                    if (d>fx[i]+fy[j]-e[i][j])
                    {
                        d=fx[i]+fy[j]-e[i][j];
                        //選出邊差的最小值
                    }
                }
            }
        }
    }
    //對於每個存在vx中的
    //和每個存在vy中的
    for (i=1;i<=n ;i++ )
    {
        if (vx[i]==1)
        {
            fx[i] -= d;
            vx[i]=0;//取消標記
        }
        if (vy[i]==1)
        {
            fy[i] += d;
            vy[i]=0;
        }
    }
    //這樣,由於vx比vy總是要多,所以,總體減小得
    return ;
}
View Code

有什么用呢?就是用來比較是不是應該這樣匹配。此話怎講?就是,開始的時候,A和Y搭配了,輪到B選人了,B說:我要和Y搭配,“為什么?“A說,B:…….對啊,為什么?大家都是最大值,憑什么我讓你?這樣,他們產生了隔閡,好吧,KM算法幫你們吧,KM:你們大家都覺得自己說最大的,但是你們能不能為全局想想,我要的是全局最大而不是你們自己最大。這樣吧,A,你除了搭配Y外,搭配其他的,相對於搭配Y來說,你的力量值會減小多少?A:減少1,我和X搭配有6,KM:好好,那么,B一樣,你除了搭配Y,的力量值減小多少?B:減少1,我和X搭配的話,力量有4。此時我們用d來記錄他們的最小差值。現在d=1;此時:fx和fy做出了相應的變化。先講規律再解釋:那就是:所有vx[i]==1的i,fx[i] -= d;所有的vy[i]==1的i,fy[i] += d;就是所有主動去過搭配別人的,因為產生過歧義,所以大家都降低了自己的要求。而所有被人邀請過的,都擺起了架子。這樣,由於,vx[]的數目總是比vy[]的數目多的。為什么?你想啊,你本來標記vy[i]=1;是有多困難啊,比標記vx[]難很多。這樣,總體來說,fx[]和fy[]的和總體減少,這樣,就為我們添加一條新的邊提供了空間。此時,我們再一次為B找匹配,先來看看fx和fy的值

     fx[]={6,4,7}  fy[]={0,1,0},這樣,B就能匹配去X了。KM:那么就是大家減少都一樣, B你就先搭配X吧。B:OK,為全局考慮。

后面的處理,其實是一樣的,到C了,如果認真的同學,一步一步運算的話,可以發現,C沒得搭配,本來C是去搭配Y的,但是,fy[2]=1,他已經擺起了架子,所以,C這么大要求已經不能滿足Y了,所以,C自己再進行一次KM算法,C減去了1(一步一步模擬,就知道為什么是減去1而不是減去2了),就能搭配Y啦,喂喂喂!好玩嗎?Y是A的,你們干嘛了,同理,發現A去搭配X是6減小1,而C搭配X是5減小2,所以,A就搭配X啦,B:………X不是我的嗎?同理,發現X搭配Z是1(此時X已經不能搭配Y了,因為在遞歸中,vy[2]已經被標記),減小3,A搭配Z是5減小1,這樣,A就搭配了Z了,到此,算法結束,最優解產生,A搭配Z是5,B搭配X是4,C搭配Y是7。

最優解的ans=16,已經可以打敗土匪了,累……去來旅游不容易啊,也看出了,生活處處是算法啊。

      本人初出茅廬,如有任何錯誤,希望讀者指出,本人感覺不盡。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM