【白话系列】二分图匹配

【序言】

        还记得自己当初学二分图匹配的时候……(哇,感觉时间过得好快好快啊)我翻看了很多的资料,不知道是不是因为我笨这个非常特殊的原因,我硬是看不下满版的公式定理概念证明代码符号等等无节操的东西,太深有感触了,就一个这么容易的东西讲那么复杂干嘛,于是我还是自己写一个作纪念好了,这里没有“X部图”“Y部图”“增广路”!这里只有那只可爱无下限的小白兔!!

【警告】

        你是人!请不要把自己当做程序员一样的思考!你只是人!请像正常人一样的思考!

【一】

        从前,在大森林里面,有一群可爱的小白兔,为什么叫它们可爱的小白兔呢?因为它们非常的可爱。

        兔子们转眼都长大了,长大了就要结婚,作为兔子国的月老(不要推辞了就是你!),你的任务是钦点夫妻,当然你已经知道了它们的恋爱关系,如下图所示。为了兔子王国的未来,同样作为兔子的你,该怎么钦点才能让夫妻最多呢?(假设你是一只有良知的兔子)

        Ps.为了表示对于崇高的兔子的尊敬,我们用框框来代表兔子,并且用男女表示性别。

【白话系列】二分图匹配_第1张图片

        哈哈哈哈,我已经听到了你的笑声,作为一只聪明的兔子,你一下就分出来了,哇塞!好厉害啊!(哼哼,等一下,难道有人不会么!!(愤怒))

【二】

        又过了好多好多年,兔子也开始像人类一样无节操了,它们!它们!它们!竟然妄想一夫多妻或者是一妻多夫!!天哪,我的三观!求求你快来拯救兔子世界。

我想你已经看到了,作为一个正常的人类,我只能为你这只兔子而感到悲哀!这也太不像话了!(难道女兔2号就是传说中的白富美?)

【白话系列】二分图匹配_第2张图片

        其实这个问题交给任何一个正常的人,他都会这样做:

                    男兔1号,我命令你娶了女兔1号,OK,一边去。

                    男兔2号,我命令你娶了女兔6号,OK,一边去。

                    男兔3号,我命令你娶了女兔2号,OK,一边去。

                    男兔4号,我命令你娶了……不好,你的女朋友不要你了,好的,你可以走了。

                    男兔5号,我命令你娶了……怎么又被抢了,白富美热销啊,你走吧。

                    男兔6号,我命令你娶了……白富美没了,你娶4号吧,OK,一边去。

                    完了?没有完啊!日子不好过啊,男兔4号与5号斧头刀子砸你家门啊,谁要你没有满足他们的心愿!

                   于是你开始追根溯源,原来它们喜欢的是白富美2号!让我看看白富美被谁抢了呢?啊,原来是男兔3号,于是你开始调查男兔3号的恋爱背景,秘密就这么被你发现了,它竟然对白富美不忠心!它还喜欢没人要的女兔5号!天哪,这么不忠诚的人有什么资格娶白富美小姐呢!速度贬下去,钦点给没人要的女兔5号以示惩罚,那么现在争抢的就只有尊贵的白富美小姐了,而且追求者们还是那么的忠诚,心儿都在它的身上,绝无杂念,这可怎么是好呢,作为一名非常公平的月老兔子,你决定把白富美送给4号,谁要4号给你送礼了呢!于是乎,出现了5对夫妻,比刚才可多了1对,功劳啊功劳,还能再多吗?我想是不行了,那么就这么定了吧。

        这么个顺利成章满足正常人思维的分配方式,没错,它就是大名鼎鼎的匈牙利算法。

        不过跟正常人思维有丁点儿不一样的是,它真正实现的时候,是在发现男兔4号的女朋友被抢了后就立马开始调查3号的身份的,而不是像上面描述的那样等到所有的人都分配完,并且家门被砸了后再开始调查,除开这一点不一样,想法都是一模一样的了。

 1 function find(i:longint):boolean; // 上文所述的对一只兔子的恋爱背景进行检查过程
 2 var
 3     j,k:longint;
 4 begin
 5     if p[i] then exit(false);  // 如果这只兔子在这轮的钦点中已经被检查过背景了,就退出
 6     p[i]:=true;  // 提前将这只兔子标记为检查完毕
 7     k:=h[i];  // 邻接表操作,获取该兔子的恋爱信息
 8     while k<>0 do begin
 9         j:=g[k];  // 获取恋爱对象
10         if (y[j]=0)or(find(y[j])) then begin  // 如果心仪对象单身或者其丈夫不忠诚而被月老贬给了别人
11             x[i]:=j;y[j]:=i;exit(true);  // 调整夫妻关系,返回一个成功信息
12         end;
13         k:=next[k];  // 若该轮不成功,物色下一个恋爱对象
14     end;
15     exit(false);  // 反正就是不成功,没办法了
16 end;


  注意事项:

        1、x[]记录的是男兔的妻子,y[]记录的是女兔的丈夫

        2、出于对女性兔子的尊重,月老只会检查男性兔子的恋爱背景

        3、返回的不成功信息有多重含义

     我知道你觉得莫名其妙,不要急,往下看……

step1、标记男兔1号检查完毕,找到女兔1号配对


【白话系列】二分图匹配_第3张图片

step2、清空所有检查完毕标记

【白话系列】二分图匹配_第4张图片

step3、标记男兔2号检查完毕,找到女图6号配对

【白话系列】二分图匹配_第5张图片

step4、清空检查完毕标记

【白话系列】二分图匹配_第6张图片

step5、标记3号男兔检查完毕,找到2号女兔配对

【白话系列】二分图匹配_第7张图片

step6、清空检查完毕标记

【白话系列】二分图匹配_第8张图片

step7、检查男兔4号检查完毕,欲配对女兔2

【白话系列】二分图匹配_第9张图片

step8、标记男兔3号已检查,开始检查3号是否忠诚

【白话系列】二分图匹配_第10张图片

step9、发现3号不忠诚,将其贬给女兔5号,之后将女兔2号赐给男兔4号,并清除标记

【白话系列】二分图匹配_第11张图片

step10、标记男兔5号为检查完毕,欲使女兔2号与其配对

【白话系列】二分图匹配_第12张图片

step11、发现女兔2号已经被分配,检查其丈夫4号是否可以迁就

【白话系列】二分图匹配_第13张图片

step12、男兔4号忠心耿耿无法迁就,男兔5号配对失败,清除标记

【白话系列】二分图匹配_第14张图片

step13、标记男兔6号为检查完毕,欲使女兔2号与其配对

【白话系列】二分图匹配_第15张图片

step14、发现女兔2号已经被分配,检查其丈夫是否可以迁就

【白话系列】二分图匹配_第16张图片

step15、男兔4号忠心耿耿无法迁就,配对失败,男兔6号试探下一个对象女兔4号成功

【白话系列】二分图匹配_第17张图片

step16、清除检查完毕标记

【白话系列】二分图匹配_第18张图片

        至此,通过伟大的匈牙利算法,最大的匹配方案就出来了,这与我们人的思维是大题一致的。

        匈牙利算法有一个重要的细节,那就是不停标记,再在一次匹配成功后清除所有标记,在刚才的算法模拟中,似乎我们并没有看到标记起了任何作用,那标记的作用在哪里??

        不急,且看下面这种情况:

【白话系列】二分图匹配_第19张图片

        目前正在为2进行配对检查,欲赐给它的是女兔1号,然而女兔1号已有丈夫男兔1号,按照算法流程,接下来将检查男兔1号的忠诚与否,这个检查过程是怎样的呢?其实就是把男兔1号当成是正在进行配对检查的男兔2号一样,看看若女兔1号不存在,男兔1号还能找到别的归宿吗?然而不可避免的是,男兔1号在检查的时候又会找到女兔1号,按照算法流程,它又会继续检验男兔1号是否能找到新的归宿,这就避免不了出现死循环!按照我们刚才的要求,是不允许男兔1号继续访问女兔1号了,因为此时我们不承认女兔1号的存在,那么我们在访问到男兔1号时,就将其标记为已检查,再次通过女兔1号而访问到男兔1号时,根据代码第一句话可知,算法将发现男兔1号已访问,于是自动退出女兔1号的访问过程,从而屏蔽了继续对女兔1号的访问。

        标记法的核心在于:我们访问女兔,并不是为了访问女兔,只是希望通过访问女兔来间接访问到它的丈夫,如果它的丈夫已经标记为不可继续访问,那么相当于这只女兔被标记为不可继续访问,从而优美地实现了屏蔽。


         啊,兔子活得真累,我觉得我也是,匈牙利算法就这么多了,其实多想想挺好理解的。

        对于二分图匹配,还有一个更加高效的算法,那就是HK算法,当然它的思想还是跟匈牙利一样,只不过进行了标号优化,这里就不赘述了。

                                                                                                                                                                                    完

你可能感兴趣的:(匈牙利算法,二分图匹配)