二分圖最大匹配(匈牙利演算法)
二分圖最大匹配
首先說明幾個概念:
1、一張圖 G是二分圖當且僅當 G 的點集 V 可以分為兩個點集 V0,V1,滿足V0υV1=V,V0∩V1=ø。對於G的每條邊e,e的兩個端點屬於兩個不同的點集。
用自然語言來描述就是,一張圖是二分圖,當且僅當它的點可以被分成兩部分,而這張圖上的所有邊的兩個端點,都分屬不同的部分。
2、二分圖G的一個匹配是G的一個邊集E0,滿足其中的所有邊都沒有公共端點。
用自然語言來描述就是,一張圖的一個匹配是一些沒有公共端點的邊。
3、增廣路:若P是圖G中一條連通兩個未匹配頂點的路徑,並且屬於M的邊和不屬於M的邊(即已匹配和待匹配的邊)在P上交替出現,則稱P為相對於M的一條增廣路徑(舉例來說,有A、B集合,增廣路由A中一個點通向B中一個點,再由B中這個點通向A中一個點……交替進行)。
如果你仔細讀過並畫過圖,不難發現如果找到一條增廣路,那麼配對的個數就會加1。 所以說,增廣路的本質其實就是一條路徑的起點和終點都未配對的點的邊。
複雜度:
時間複雜度 : 鄰接矩陣最壞為O(n^3)
鄰接表: O(mn)
空間複雜度 : 鄰接矩陣:O(n^2)
鄰接表: O(n+m)
匈牙利演算法:
匈牙利演算法是二分圖匹配最常見的演算法,該演算法的核心就是尋找增廣路徑,它是一種用增廣路徑求二分圖最大匹配的演算法。
那下面該講下匈牙利演算法了!!!
根據上文的描述,既然增廣路的作用是“改進匹配方案”(即增加配對數),那麼如果我們已經找到了一種匹配方案,不難發現如果在當前匹配方案下再也找不到任何增廣路的話,那麼當前匹配就是二分圖的最大匹配,演算法如下。
1.首先從任意的一個未配對的點u開始,從點u的邊中任意選一條邊(假設這條邊是從u->v)開始配對。如果點v未配對,則配對成功,這是便找到了一條增廣路。如果點v已經被配對,就去嘗試“連鎖反應”,如果這時嘗試成功,就更新原來的配對關係。
所以這裡要用一個match[v] = u。配對成功就將配對數加1。
2.如果剛才所選的邊配對失敗,那就要從點u的邊中重新選一條邊重新去試。直到點u 配對成功,或嘗試過點u的所有邊為止。
3.接下來就繼續對剩下的未配對過的點一一進行配對,直到所有的點都已經嘗試完畢,找不到新的增廣路為止。
4.輸出配對數。
以上,就是匈牙利演算法的全部內容。
CODE:
1 #include <bits/stdc++.h> 2 #define N 1001 3 using namespace std; 4 int n,m,e,ans,match[N];//match用於記錄每個點所配對的點 5 bool a[N][N],vis[N]; 6 bool dfs(int x) 7 { 8 for(int i=1;i<=m;i++) 9 if (!vis[i]&&a[x][i])//vis陣列防止往回走 10 {/*這裡有兩種情況,一種是右集中的點沒有被選,那麼它們倆構成長度為1的增廣路. 11 另一種是右集中的點已經被選了,但是往下遞迴可以發現選它的點可以有其他的選擇。 12 這樣構成了一條兩端都是非匹配點的路徑,即增廣路。*/ 13 vis[i]=1; 14 if (!match[i]||dfs(match[i])) 15 { 16 match[i]=x;//更改匹配 17 return 1; 18 } 19 } 20 return 0; 21 } 22 int main() 23 { 24 cin>>n>>m>>e; 25 for(int i=1;i<=e;i++) 26 { 27 int u,v;cin>>u>>v; 28 if(v<=m) a[u][v]=1;//因為題目給出的資料可能會有比m大的。 29 } 30 for(int i=1;i<=n;i++)//列舉左集中的結點 31 { 32 ans+=dfs(i); 33 memset(vis,0,sizeof(vis));//每次跑匈牙利演算法之前要清空vis陣列(重點) 34 } 35 cout<<ans<<endl;return 0; 36 }
————THE END
by AIskeleton 2021/12/15