二分图完全匹配算法之匈牙利算法
最近在參加了一個小項目,里面用到二分圖匹配算法,因為之前并沒有接觸過相關算法,于是找到一些博客進行了一番學習,但學習之后,發現部分博客或多或少存在一些另我疑惑的地方,于是,我打算寫此博客以鞏固對算法的理解。
一、二分圖基本概念
二分圖(Bipartite graph)又稱作二部圖,是圖論中的一種特殊模型。 設G=(V,E)是一個無向圖,如果頂點V可分割為兩個互不相交的子集(A,B),并且圖中的每條邊(i,j)所關聯的兩個頂點i和j分別屬于這兩個不同的頂點集(i in A,j in B),則稱圖G為一個二分圖。
通俗一點,二分圖是一類特殊的圖,它可以被劃分為兩個部分,每個部分內的點互不相連。
如下圖所示,可以將【2】【4】結點視為一個集合,【1】【3】【5】【6】結點視為一個集合,集合內部的結點互不相連,因此該圖為一個二分圖。
重要定理:G為二分圖的充要條件是G中的每一個圈的長度都是偶數。
二、二分圖匹配相關概念
1、什么是匹配?
注意,本文只討論二分圖的最大匹配,不討論最優匹配。
給定一個二分圖G=(V,E),在G的一個子圖M中,M的邊集{E}中的任意兩條邊都不依附于同一個頂點,則稱M是一個匹配;具有邊數量最多的匹配稱為最大匹配;若|V|=2|M|,則稱M為完美匹配。
如下所示為一個二分圖的完美匹配:
二分圖完美匹配一定是最大匹配,最大匹配不一定是完美匹配。
2、什么是交替路與增廣路?
交替路:從一個未匹配點出發,依次經過非匹配邊、匹配邊、非匹配邊…形成的路徑叫交替路。
增廣路(Augmenting Path):從一個未匹配點出發,走交替路,如果途徑另一個未匹配點(出發點不算),則這條【交替路】稱為增廣路。
增廣路的三個性質:
增廣路的這三個性質可以用來尋找匹配,在接下來會說明。
如下圖所示:
路徑2->A->1->B為一條交替路,因為出發點2和途徑點B均為未匹配點,因此該路徑也為增廣路。
三、匈牙利算法
1、基本概念
匈牙利算法的核心就是尋找增廣路。具體而言,匈牙利算法從二分圖中沒有匹配的點開始尋找增廣路徑(增廣路徑性質3)。找到增廣路后,根據增廣路徑的性質,顯然路徑里沒被匹配的連線比已經匹配了的連線多一條(增廣路徑性質2),于是對增廣路徑中的邊進行取反操作,這樣匹配數就比原來多1個。不斷執行上述操作,直到找不到增廣路徑為止,無法找到增廣路徑說明已達到最大匹配(增廣路徑性質1)。
取反操作:將增廣路的匹配邊變成不匹配邊,不匹配邊變成匹配邊。
如上圖所示:
注:顯然,二分圖匹配結果并不唯一。
以上就是匈牙利算法的過程,很明顯,搜尋增廣路徑這個步驟是基于深度優先搜索的。
匈牙利算法尋找增廣路的偽代碼描述:
//以下為尋找增廣路并進行取反操作的偽代碼,取反操作通過遞歸調用完成。 while(找到Xi的關聯頂點Yj){if(頂點Yj不在增廣路徑上){將Yj加入增廣路;//如果Yj是未匹配點說明成功找到增廣路,從“Yj處繼續尋找能夠找到增廣路”屬于遞歸調用本函數過程,進行取反操作if(Yj是未匹配點||從Yj處繼續尋找能夠找到增廣路){ 將Yj的匹配點改為Xi;return true;}}return false; }2、匈牙利算法的實現
網上搜索匈牙利算法,從思想上看都是基于dfs的,另外,我注意到也有一些博客提出了基于廣度優先(bfs)的匈牙利算法,我不確定所謂“基于bfs的匈牙利算法”能否稱為匈牙利算法,我個人的理解是匈牙利算法就是基于dfs的來尋找增廣路徑而實現的。
2.1、實際問題
問題描述
RPG girls今天和大家一起去游樂場玩,終于可以坐上夢寐以求的過山車了。可是,過山車的每一排只有兩個座位,而且還有條不成文的規矩,就是每個女生必須找個個男生做partner和她同坐。但是,每個女孩都有各自的想法,舉個例子把,Rabbit只愿意和XHD或PQK做partner,Grass只愿意和linle或LL做partner,PrincessSnow愿意和水域浪子或偽酷兒做partner。考慮到經費問題,boss劉決定只讓找到partner的人去坐過山車,其他的人,嘿嘿,就站在下面看著吧。聰明的Acmer,你可以幫忙算算最多有多少對組合可以坐上過山車嗎?
輸入:
輸入數據的第一行是三個整數K , M , N,分別表示可能的組合數目,女生的人數,男生的人數。0<K<=1000
1<=N 和M<=500.接下來的K行,每行有兩個數,分別表示女生Ai愿意和男生Bj做partner。最后一個0結束輸入。
輸出:對于每組數據,輸出一個整數,表示可以坐上過山車的最多組合數。
樣例輸入:6 3 3 1 1 1 2 1 3 2 1 2 3 3 1 0
樣例輸出:3
這道題本質上就是求二分圖的最大匹配問題。
2.2、代碼實現
Java實現:
此代碼轉載自https://wmathor.com/index.php/archives/1344/,//***//表示是我添加的注釋。
import java.util.Arrays; import java.util.Scanner;public class Main {static int[][] map;static int n, m;public static void main(String[] args) {Scanner cin = new Scanner(System.in);while (cin.hasNext()) {int t = cin.nextInt();if (t == 0)break;n = cin.nextInt();m = cin.nextInt();map = new int[n + 1][m + 1];for (int i = 0; i < t; i++)map[cin.nextInt()][cin.nextInt()] = 1; // 有向邊int count = 0; // 最大匹配數int[] mc = new int[m + 1]; // mc[i] = j 表示i號男生所連的女生是j號Arrays.fill(mc, -1); // 初始時所有女生都沒有連//***//以下for循環是核心部分,表示依次從未匹配點開始尋找增廣路徑for (int i = 1; i <= n; i++) {//***//將增廣路徑中的結點記錄下來,以免重復,因此每一次循環都必須重置。boolean[] vis = new boolean[m + 1]; // vis[i] = true 表示i號男生已經被匹配了//***//根據增廣路徑性質2、3,每找到一條增廣路徑就說明找到了一條匹配邊if (dfs(i, vis, mc))count++;}System.out.println(count);}}//***//注:這個是尋找增廣路徑的代碼,可以結合之前的偽代碼,每找到一條增廣路徑就說明找到了一條匹配邊。private static boolean dfs(int start, boolean[] vis, int[] mc) {for (int i = 1; i <= m; i++) { // 枚舉男生集if (!vis[i] && map[start][i] == 1) { // 如果這個男生沒被匹配并且和當前的start有邊相連vis[i] = true; // 將這個男生標記為匹配過//***//以下判斷語句中dfs遞歸調用,對應匈牙利算法中尋找增廣路徑并取反的操作,大家可以試著手動推理一下if (mc[i] == -1 || dfs(mc[i], vis, mc)) { // 這個男生沒有和女生匹配 || 這個男生所連的女生還有別的選擇,就把這個男生"騰"出來mc[i] = start;return true;}}}return false;} }Go:
有時間再把go的代碼實現并添加過來,先畫個餅。四、總結
匈牙利算法的核心在于找增廣路徑,注意增廣路徑的三個性質。文中偽代碼部分可以當成是解決這類問題的模板。
本文內容也是我自己對于算法的理解,可能存在錯誤,歡迎批評指正。
五、參考博客
https://blog.csdn.net/lemonxiaoxiao/article/details/108672039
https://zhuanlan.zhihu.com/p/208596378
https://www.cxyxiaowu.com/874.html
總結
以上是生活随笔為你收集整理的二分图完全匹配算法之匈牙利算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于灰度的模板匹配算法
- 下一篇: 松下FPX通用通信编程实例