二分圖之匈牙利算法
(⊙o⊙)…——————————————————————————————————————————————————————————————————前面的概念大多使用網上粘的(ORZ)
one、基本概念
1. 二分圖
二分圖是圖論中的一種特殊模型。若能將無向圖G=(V,E)的頂點V劃分為兩個交集為空的頂點集,並且任意邊的兩個端點都分屬於兩個集合,則稱圖G為一個為二分圖。
2.匹配
一個匹配即一個包含若幹條邊的集合,且其中任意兩條邊沒有公共端點。如下圖,圖3的紅邊即為圖2的一個匹配。
有關匹配
①最大匹配
在G的一個子圖M中,M的邊集中的任意兩條邊都不依附於同一個頂點,則稱M是一個匹配。選擇這樣的邊數最大的子集稱為圖的最大匹配問題,最大匹配的邊數稱為最大匹配數.如果一個匹配中,圖中的每個頂點都和圖中某條邊相關聯,則稱此匹配為完全匹配,也稱作完備匹配。如果在左右兩邊加上源匯點後,圖G等價於一個網絡流,最大匹配問題可以轉為最大流的問題。解決此問的匈牙利算法的本質就是尋找最大流的增廣路徑。上圖中的最大匹配如下圖紅邊所示:
②最優匹配
最優匹配又稱為帶權最大匹配,是指在帶有權值邊的二分圖中,求一個匹配使得匹配邊上的權值和最大。一般X和Y集合頂點個數相同,最優匹配也是一個完備匹配,即每個頂點都被匹配。如果個數不相等,可以通過補點加0邊實現轉化。一般使用KM算法解決該問題。
③最小覆蓋
二分圖的最小覆蓋分為最小頂點覆蓋和最小路徑覆蓋:
①最小頂點覆蓋是指最少的頂點數使得二分圖G中的每條邊都至少與其中一個點相關聯,二分圖的最小頂點覆蓋數=二分圖的最大匹配數;
②最小路徑覆蓋也稱為最小邊覆蓋,是指用盡量少的不相交簡單路徑覆蓋二分圖中的所有頂點。二分圖的最小路徑覆蓋數=|V|-二分圖的最大匹配數;
④最大獨立集
最大獨立集是指尋找一個點集,使得其中任意兩點在圖中無對應邊。對於一般圖來說,最大獨立集是一個NP完全問題,對於二分圖來說最大獨立集=|V|-二分圖的最大匹配數。如下圖中黑色點即為一個最大獨立集:
基本概念—匈牙利算法
交替路:從一個未匹配點出發,依次經過非匹配邊、匹配邊、非匹配邊...形成的路徑叫交替路。*
增廣路:從一個未匹配點出發,走交替路,如果途徑另一個未匹配點(出發的點不算),則這條交替路稱為增廣路(agumenting path)。
3.最大匹配與最小點覆蓋
最小點覆蓋:假如選了一個點就相當於覆蓋了以它為端點的所有邊,你需要選擇最少的點來覆蓋所有的邊
最小割定理是一個二分圖中很重要的定理:一個二分圖中的最大匹配數等於這個圖中的最小點覆蓋數。
最小點集覆蓋==最大匹配。在這裏解釋一下原因,首先,最小點集覆蓋一定>=最大匹配,因為假設最大匹配為n,那麽我們就得到了n條互不相鄰的邊,光覆蓋這些邊就要用到n個點。現在我們來思考為什麽最小點擊覆蓋一定<=最大匹配。任何一種n個點的最小點擊覆蓋,一定可以轉化成一個n的最大匹配。因為最小點集覆蓋中的每個點都能找到至少一條只有一個端點在點集中的邊(如果找不到則說明該點所有的邊的另外一個端點都被覆蓋,所以該點則沒必要被覆蓋,和它在最小點集覆蓋中相矛盾),只要每個端點都選擇一個這樣的邊,就必然能轉化為一個匹配數與點集覆蓋的點數相等的匹配方案。所以最大匹配至少為最小點集覆蓋數,即最小點擊覆蓋一定<=最大匹配。綜上,二者相等。
4.無聊的講解(這來自學長博客)
由增廣路的性質,增廣路中的匹配邊總是比未匹配邊多一條,所以如果我們放棄一條增廣路中的匹配邊,選取未匹配邊作為匹配邊,則匹配的數量就會增加。匈牙利算法就是在不斷尋找增廣路,如果找不到增廣路,就說明達到了最大匹配。
先給一個例子
1、起始沒有匹配
2、選中第一個x點找第一跟連線
3、選中第二個點找第二跟連線
4、發現x3的第一條邊x3y1已經被人占了,找出x3出發的的交錯路徑x3-y1-x1-y4,把交錯路中已在匹配上的邊x1y1從匹配中去掉,剩余的邊x3y1 x1y4加到匹配中去
5、同理加入x4,x5。
匈牙利算法可以深度有限或者廣度優先,剛才的示例是深度優先,即x3找y1,y1已經有匹配,則找交錯路。若是廣度優先,應為:x3找y1,y1有匹配,x3找y2。
two、趣味講解
上面的東西有沒有看懵??好吧,我們來看一個生動有趣的講解
首先我們有n個王子和n個女孩。現在你是皇上,你要給他們指婚,你當然需要盡可能的把這些女孩都與王子匹配上。我們已知這些王子都有自己心儀的女孩,女孩也都有自己夢中的白馬王子,(當然,身為一位明君,你是不會硬生生的將這些有情人拆開的。。)
本著救人一命,勝造七級浮屠的原則,你想要盡可能地撮合更多的情侶,匈牙利算法的工作模式會教你這樣做:
===============================================================================
一: 先試著給1號王子找妹子,發現第一個和他相連的1號女生還名花無主,got it,連上一條紅線
===============================================================================
二:接著給2號男生找妹子,發現第一個和他相連的3號女生名花無主,got it
===============================================================================
三:接下來是3號男生,很遺憾1號女生已經有主了,怎麽辦呢?
我們試著給之前1號女生匹配的男生(也就是1號男生)另外分配一個妹子。
(黃色表示這條邊被臨時拆掉)
與1號男生相連的第二個女生是2號女生,但是2號女生沒主,來牽上紅線。
來我們再來匹配3號王子(這是他正好與1號女孩牽上紅繩)
來再來4號王子。這麽辦,這位帥帥的王子的心儀女孩也被牽走了。。。怎麽辦呢?我們再試著給2號女生的原配()重新找個妹子(註意這個步驟和上面是一樣的,這是一個遞歸的過程)
此時發現2號男生還能找到4號女生,那麽之前的問題迎刃而解了,回溯回去
來,給4號王子連上紅繩。
2號王子可以找4號妹子~~~ 1號男生可以找2號妹子了~~~ 3號男生可以找1號妹子 4號王子可以找3號妹子了。。
所以第三步最後的結果就是:
===============================================================================
四: 接下來是5號王子,很遺憾,按照第三步的節奏我們沒法給5號王子騰出來一個公主,我們實在是無能為力了…(那就公豬吧,湊活湊活吧。。。。)
===============================================================================
這就是匈牙利算法的流程,其中找妹子是個遞歸的過程,最最關鍵的字就是“騰”字其原則大概是:有機會上,沒機會創造機會也要上
three、代碼呈現
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 1010 using namespace std; bool vis[N]; int n1,n2,m,x,y,ans,girl[N],map[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘) {if(ch==‘-‘) f=-1; ch=getchar();} while(ch<=‘9‘&&ch>=‘0‘){x=x*10+ch-‘0‘;ch=getchar();} return x*f; } int find(int x) { for(int i=1;i<=n2;i++) if(map[x][i]&&!vis[i]) { vis[i]=true; if(!girl[i]||find(girl[i])) { girl[i]=x; return 1; } } return 0; } int main() { n1=read(),n2=read(),m=read(); for(int i=1;i<=m;i++) x=read(),y=read(),map[x][y]=1; for(int i=1;i<=n1;i++) { memset(vis,0,sizeof(vis)); if(find(i)) ans++; } printf("%d",ans); return 0; }
二分圖之匈牙利算法