日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

[转]带花树,Edmonds's matching algorithm,一般图最大匹配

發(fā)布時(shí)間:2023/11/30 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [转]带花树,Edmonds's matching algorithm,一般图最大匹配 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  看了兩篇博客,覺(jué)得寫得不錯(cuò),便收藏之。。

  首先是第一篇,轉(zhuǎn)自某Final牛

帶花樹……其實(shí)這個(gè)算法很容易理解,但是實(shí)現(xiàn)起來(lái)非常奇葩(至少對(duì)我而言)。

除了wiki和amber的程序我找到的資料看著都不大靠譜

比如昨晚找到一篇鄙視帶花樹的論文,然后介紹了一種O(E)的一般圖最大匹配……我以為找到了神論文,然后ACM_DIY眾神紛紛表示這個(gè)是錯(cuò)的……于是神論文成為了”神論文“……

又比如圍觀nocow上帶花樹標(biāo)程,一看……這顯然是裸的匈牙利算法……貨不對(duì)板啊

當(dāng)然……如果二分圖的匈牙利算法還不會(huì)請(qǐng)先圍觀求二分圖最大匹配的匈牙利算法。

?

實(shí)際上任意圖求最大匹配也是找增廣路,但是由于奇環(huán)的出現(xiàn),找增廣路變得困難。

首先明確一點(diǎn),增廣路上是不能有重復(fù)出現(xiàn)的點(diǎn)的。

??
?

二分圖中,匹配邊可以看作是有向的,比如定義總是從X集指向Y集。假若定義了起點(diǎn)必須在X集中,那么增廣路中出現(xiàn)該匹配邊時(shí),必然是按照這個(gè)方向的。所以一個(gè)點(diǎn)在增廣路中的奇偶性是確定的。

而這個(gè)圖中,從增廣路3->1->4->5和2->4->1->6可以看出,對(duì)于有奇環(huán)的任意圖,1和4這兩個(gè)點(diǎn)在增廣路中所在位置的奇偶性不再一定。于是我們考慮處理這些奇環(huán)。

?

定義奇環(huán):包含2k+1個(gè)點(diǎn)和k條匹配邊的一個(gè)環(huán)。(如果不是這樣,我們找增廣路不會(huì)走上去)

對(duì)于這個(gè)奇環(huán),k條匹配覆蓋了2k個(gè)點(diǎn),那么顯然有一個(gè)點(diǎn)未被覆蓋。我們拿出這個(gè)點(diǎn)來(lái)討論。

比如圖中的1號(hào)點(diǎn)就是這個(gè)這個(gè)特殊的點(diǎn)。除了這個(gè)點(diǎn)以外,其它的點(diǎn)都被覆蓋了,所以只能向外連非匹配邊,而1號(hào)點(diǎn)可以向外連匹配邊
或非匹配邊。

如果1號(hào)點(diǎn)沒(méi)有被外面的點(diǎn)匹配,那么無(wú)論從其它的哪個(gè)點(diǎn)走進(jìn)來(lái),都能以1為終點(diǎn)找到增廣路。(要么順時(shí)針跑到1,要么逆時(shí)針)

同理如果1號(hào)點(diǎn)被外面的點(diǎn)匹配了,那么無(wú)論從其它的哪個(gè)點(diǎn)走進(jìn)來(lái),都能把這個(gè)圈看成一個(gè)點(diǎn),然后從1的那條匹配邊穿出去。(要么順時(shí)針,要么逆時(shí)針)

?于是這個(gè)奇環(huán)就可以看成一個(gè)點(diǎn),其主要特性由1號(hào)點(diǎn)體現(xiàn)(諸如和誰(shuí)匹配了之流)。

這個(gè)合成點(diǎn)就叫做花。這個(gè)算法的思想就是不斷地把奇環(huán)合成點(diǎn),直至找到增廣路(合成了某朵花以后就把整朵花當(dāng)成一個(gè)點(diǎn))。

考慮用BFS搜索增廣路。

圍觀wiki這個(gè)圖

?

由于BFS的性質(zhì),我們找到奇環(huán)只能是和同層的點(diǎn),或者下下一層的點(diǎn)。

然后奇環(huán)的關(guān)鍵點(diǎn)必然是這棵BFS樹里深度最淺的點(diǎn)。然后考慮合成以后,花如何展開對(duì)應(yīng)的路徑,使得我們能夠增廣。

花套花這個(gè)東西想起來(lái)都糾結(jié)>_<。

amber的程序里面并沒(méi)有把點(diǎn)真的合成,只是弄了一個(gè)表示集合的標(biāo)號(hào):Base,然后鄰接矩陣就不用變來(lái)變?nèi)チ恕?/p>

對(duì)于花中連向父親的是匹配邊的點(diǎn),他的增廣路顯然是直接順著父親走,而如果連向父親的邊是非匹配邊的點(diǎn),那么顯然是往后走然后跑過(guò)紅色的橫插邊,然后再向上跑回關(guān)鍵點(diǎn)。

注意到如果連向父子的邊是匹配邊的點(diǎn)原先是不需要Father這個(gè)域來(lái)描述的,直接用表示匹配的那個(gè)域就可以了。但是現(xiàn)在在花中,他的Father這個(gè)域就要起作用了,用來(lái)向后指向,然后繞過(guò)紅色橫插邊然后再跑回關(guān)鍵點(diǎn)。

實(shí)在是太精妙了。

1 //Problem:http://acm.timus.ru/problem.aspx?space=1&num=1099 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cstring> 5 #include <iostream> 6 #include <algorithm> 7 using namespace std; 8 const int N=250; 9 int n; 10 int head; 11 int tail; 12 int Start; 13 int Finish; 14 int link[N]; //表示哪個(gè)點(diǎn)匹配了哪個(gè)點(diǎn) 15 int Father[N]; //這個(gè)就是增廣路的Father……但是用起來(lái)太精髓了 16 int Base[N]; //該點(diǎn)屬于哪朵花 17 int Q[N]; 18 bool mark[N]; 19 bool map[N][N]; 20 bool InBlossom[N]; 21 bool in_Queue[N]; 22 23 void CreateGraph(){ 24 int x,y; 25 scanf("%d",&n); 26 while (scanf("%d%d",&x,&y)!=EOF) 27 map[x][y]=map[y][x]=1; 28 } 29 30 void BlossomContract(int x,int y){ 31 fill(mark,mark+n+1,false); 32 fill(InBlossom,InBlossom+n+1,false); 33 #define pre Father[link[i]] 34 int lca,i; 35 for (i=x;i;i=pre) {i=Base[i]; mark[i]=true; } 36 for (i=y;i;i=pre) {i=Base[i]; if (mark[i]) {lca=i; break;} } //尋找lca之旅……一定要注意i=Base[i] 37 for (i=x;Base[i]!=lca;i=pre){ 38 if (Base[pre]!=lca) Father[pre]=link[i]; //對(duì)于BFS樹中的父邊是匹配邊的點(diǎn),Father向后跳 39 InBlossom[Base[i]]=true; 40 InBlossom[Base[link[i]]]=true; 41 } 42 for (i=y;Base[i]!=lca;i=pre){ 43 if (Base[pre]!=lca) Father[pre]=link[i]; //同理 44 InBlossom[Base[i]]=true; 45 InBlossom[Base[link[i]]]=true; 46 } 47 #undef pre 48 if (Base[x]!=lca) Father[x]=y; //注意不能從lca這個(gè)奇環(huán)的關(guān)鍵點(diǎn)跳回來(lái) 49 if (Base[y]!=lca) Father[y]=x; 50 for (i=1;i<=n;i++) 51 if (InBlossom[Base[i]]){ 52 Base[i]=lca; 53 if (!in_Queue[i]){ 54 Q[++tail]=i; 55 in_Queue[i]=true; //要注意如果本來(lái)連向BFS樹中父結(jié)點(diǎn)的邊是非匹配邊的點(diǎn),可能是沒(méi)有入隊(duì)的 56 } 57 } 58 } 59 60 void Change(){ 61 int x,y,z; 62 z=Finish; 63 while (z){ 64 y=Father[z]; 65 x=link[y]; 66 link[y]=z; 67 link[z]=y; 68 z=x; 69 } 70 } 71 72 void FindAugmentPath(){ 73 fill(Father,Father+n+1,0); 74 fill(in_Queue,in_Queue+n+1,false); 75 for (int i=1;i<=n;i++) Base[i]=i; 76 head=0; tail=1; 77 Q[1]=Start; 78 in_Queue[Start]=1; 79 while (head!=tail){ 80 int x=Q[++head]; 81 for (int y=1;y<=n;y++) 82 if (map[x][y] && Base[x]!=Base[y] && link[x]!=y) //無(wú)意義的邊 83 if ( Start==y || link[y] && Father[link[y]] ) //精髓地用Father表示該點(diǎn)是否 84 BlossomContract(x,y); 85 else if (!Father[y]){ 86 Father[y]=x; 87 if (link[y]){ 88 Q[++tail]=link[y]; 89 in_Queue[link[y]]=true; 90 } 91 else{ 92 Finish=y; 93 Change(); 94 return; 95 } 96 } 97 } 98 } 99 100 void Edmonds(){ 101 memset(link,0,sizeof(link)); 102 for (Start=1;Start<=n;Start++) 103 if (link[Start]==0) 104 FindAugmentPath(); 105 } 106 107 void output(){ 108 fill(mark,mark+n+1,false); 109 int cnt=0; 110 for (int i=1;i<=n;i++) 111 if (link[i]) cnt++; 112 printf("%d\n",cnt); 113 for (int i=1;i<=n;i++) 114 if (!mark[i] && link[i]){ 115 mark[i]=true; 116 mark[link[i]]=true; 117 printf("%d %d\n",i,link[i]); 118 } 119 } 120 121 int main(){ 122 // freopen("input.txt","r",stdin); 123 CreateGraph(); 124 Edmonds(); 125 output(); 126 return 0; 127 }

?

  然后還有一篇,鏈接請(qǐng)猛戳。。

在北京冬令營(yíng)的時(shí)候,yby提到了“帶花樹開花”算法來(lái)解非二分圖的最大匹配。

于是,我打算看看這是個(gè)什么玩意。其實(shí)之前,我已經(jīng)對(duì)這個(gè)算法了解了個(gè)大概,但是。。。真的不敢去寫。 有一個(gè)叫Galil Zvi的人(應(yīng)該叫計(jì)算機(jī)科學(xué)家),寫了篇論文: Efficient Algorithms for Finding Maximal Matching in Graphs (如果你在網(wǎng)上搜不到,可以:http://builtinclz.abcz8.com/art/2012/Galil%20Zvi.pdf) 這篇論文真神啊,它解決了4個(gè)問(wèn)題: (一般圖+二分圖)的(最大匹配+最大權(quán)匹配)問(wèn)題。 算法的思想、故事,請(qǐng)自己看論文吧。 這個(gè)論文告訴了我們很多有趣的東西,例如: 用Dinic實(shí)現(xiàn)的二分圖匹配的時(shí)間復(fù)雜度其實(shí)是O(M*N^0.5),這也許能夠解釋為什么一般網(wǎng)絡(luò)流算法比Hungry要快了。 另外,帶花樹算法的正確性的證明比較困難;而其時(shí)間復(fù)雜度是可以做到O(M*N^0.5)的,不過(guò)要詳細(xì)實(shí)現(xiàn),那么就快能到“ACM最長(zhǎng)論文獎(jiǎng)”了。 我寫了一個(gè)實(shí)例代碼:

http://builtinclz.abcz8.com/art/2012/ural1099.cpp

沒(méi)錯(cuò),這是用來(lái)解決URAL 1099 Work Schedule那題的。時(shí)間復(fù)雜度是O(N^3)

簡(jiǎn)述一下“帶花樹”算法吧: 它的核心思想還是找增廣路。假設(shè)已經(jīng)匹配好了一堆點(diǎn),我們從一個(gè)沒(méi)有匹配的節(jié)點(diǎn)s開始,使用BFS生成搜索樹。每當(dāng)發(fā)現(xiàn)一個(gè)節(jié)點(diǎn)u,如果u還沒(méi)有被匹配,那么就可以進(jìn)行一次成功的增廣;否則,我們就把節(jié)點(diǎn)u和它的配偶v一同接到樹上,之后把v丟進(jìn)隊(duì)列繼續(xù)搜索。我們給每個(gè)在搜索樹上的點(diǎn)一個(gè)類型:S或者T。當(dāng)u把它的配偶v扔進(jìn)隊(duì)列的時(shí)候,我們把u標(biāo)記為T型,v標(biāo)記為S型。于是,搜索樹的樣子是這樣的: s /        |    | c    d         u j | |  | | i j  v k 其中,黑色豎線相連的兩個(gè)點(diǎn)是已經(jīng)匹配好的,藍(lán)色斜線表示兩個(gè)點(diǎn)之間有邊,但是沒(méi)有配對(duì)。T型的用紅色,S型的用黑色。 這里有個(gè)小問(wèn)題:一個(gè)S型點(diǎn)d在某一步擴(kuò)展的時(shí)候發(fā)現(xiàn)了點(diǎn)u,如果u已經(jīng)在搜索樹上了(即,出現(xiàn)了環(huán)),怎么辦? 我們規(guī)定,如果u的類型是T型,就無(wú)視這次發(fā)現(xiàn);(這意味著我們找到了一個(gè)長(zhǎng)度為偶數(shù)的環(huán),直接無(wú)視) s /        |    | c    d   如果連出來(lái)的邊是指向T型點(diǎn)的,就無(wú)視這個(gè)邊。          | |    | i j    k 否則,我們找到了一個(gè)長(zhǎng)度為奇數(shù)的環(huán),就要進(jìn)行一次“縮花”的操作!所謂縮花操作,就是把這個(gè)環(huán)縮成一個(gè)點(diǎn)。 s /        |    | c    d           | |   | i u<-+ k 這個(gè)圖縮花之后變成了5個(gè)點(diǎn)(一個(gè)大點(diǎn),或者叫一朵花,加原來(lái)的4個(gè)點(diǎn)): 縮點(diǎn)完成之后,還要把原來(lái)環(huán)里面的T型點(diǎn)統(tǒng)統(tǒng)變成S型點(diǎn),之后扔到隊(duì)列里去。 +-------------+ |             | |     s       | |    /  \     | |   a    b    | |   |    |    |   現(xiàn)在是一個(gè)點(diǎn)了!還是一個(gè)S點(diǎn)。 |   c    d    | |     / \   | +-|--  f--u  ---|---+ | |             |   | | |        |   | | |             |   | | +-------------+   | |                   | e                   g |                   | i                   k 為什么能縮成一個(gè)點(diǎn)呢?我們看一個(gè)長(zhǎng)度為奇數(shù)的環(huán)(例如上圖中的s-b-d-j-f-c-a-),如果我們能夠給它中的任意一個(gè)點(diǎn)找一個(gè)出度(配偶),那么環(huán)中的其他點(diǎn)正好可以配成對(duì),這說(shuō)明,每個(gè)點(diǎn)的出度都是等效的。例如,假設(shè)我們能夠給圖中的點(diǎn)d另找一個(gè)配偶(例如d'好了),那么,環(huán)中的剩下6個(gè)點(diǎn)正好能配成3對(duì),一個(gè)不多,一個(gè)不少(算上d和d'一共4對(duì)剛剛好)。 a-s-b-dd'         a s-b d-d' \    |       =>    \      cf-u              c f-u 這就是我們縮點(diǎn)的思想來(lái)源。有一個(gè)勞苦功高的計(jì)算機(jī)科學(xué)家證明了:縮點(diǎn)之前和縮點(diǎn)之后的圖是否有增廣路的情況是相同的。 縮起來(lái)的點(diǎn)又叫一朵花(blossom). 注意到,組成一朵花的里面可能嵌套著更小的花。 當(dāng)我們最終找到一條增廣路的時(shí)候,要把嵌套著的花層層展開,還原出原圖上的增廣路出來(lái)。 嗯,現(xiàn)在你對(duì)實(shí)現(xiàn)這個(gè)算法有任何想法嗎? 天啊,還要縮點(diǎn)……寫死誰(shuí)。。。。。。 我一開始也是這么想的。 我看了一眼網(wǎng)上某個(gè)大牛的程序,之后結(jié)合自己的想法,很努力地寫出了一個(gè)能AC的版本。 實(shí)現(xiàn)的要點(diǎn)有什么呢? 首先,我們不“顯式”地表示花。我們記錄一個(gè)Next數(shù)組,表示最終增廣的時(shí)候的路徑上的后繼。同時(shí),我們維護(hù)一個(gè)并查集,表示每個(gè)點(diǎn)現(xiàn)在在以哪個(gè)點(diǎn)為根的花里(一個(gè)花被縮進(jìn)另一朵花之后就不算花了)。還要記錄每個(gè)點(diǎn)的標(biāo)記。 主程序是一段BFS。對(duì)于每個(gè)由x發(fā)展出來(lái)的點(diǎn)y,分4種情況討論: 1。xy是配偶(不說(shuō)夫妻,這是非二分圖。。。)或者xy現(xiàn)在是一個(gè)點(diǎn)(在一朵花里):直接無(wú)視 2。y是T型點(diǎn):直接無(wú)視 3。y目前單身:太好了,進(jìn)行增廣! 4。y是一個(gè)S型點(diǎn):縮點(diǎn)!縮點(diǎn)! 縮點(diǎn)的時(shí)候要進(jìn)行的工作: 1。找x和y的LCA(的根)p。找LCA可以用各種方法。。。直接樸素也行。 2。在Next數(shù)組中把x和y接起來(lái)(表示它們形成環(huán)了!) 3。從x、y分別走到p,修改并查集使得它們都變成一家人,同時(shí)沿路把Next數(shù)組接起來(lái)。 Next數(shù)組很奇妙。每時(shí)每刻,它實(shí)際形成了若干個(gè)掛在一起的雙向鏈表來(lái)表示一朵花內(nèi)部的走法。 ---- /    \--+ |    |   | |    |--+      ---------- /          \ +-            --+ |               | |               | +----s  ------+      有權(quán)圖的最大匹配怎么做? 看論文吧。。。用類似KM的方法,不過(guò),是給每個(gè)花再來(lái)一個(gè)權(quán)值。真的很復(fù)雜。。。 有一個(gè)人寫了代碼,好像是GPL許可證。。。你最好想辦法搜到它的網(wǎng)站來(lái)看看版權(quán)的問(wèn)題;總之,我先貼出來(lái): http://builtinclz.abcz8.com/art/2012/mwmatching.py

轉(zhuǎn)載于:https://www.cnblogs.com/zhsl/p/3271754.html

總結(jié)

以上是生活随笔為你收集整理的[转]带花树,Edmonds's matching algorithm,一般图最大匹配的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。