二分圖匹配,匈牙利演算法原理與實現
阿新 • • 發佈:2021-03-11
>文章首先於微信公眾號:幾何思維,關注第一時間獲取更新資訊
>以下場景太過真實,但都是虛構,為了講清楚理論的過程。如有雷同,純屬我瞎編,還望勿對號入座。
### 1 婚戀市場,明碼實價
中國如今男女比例嚴重失衡,2021年預計將有9200萬單身貴族。為了幫助解決這個社會性問題,提升整體人民的幸福感,小K打算投身到這份偉大的事業中。
“**幾何思維**”婚戀所,用最科學的方法,幫你脫單。通過概率論尋找最佳匹配物件,再通過微積分精確計算好感上升曲線,最後用數值分析無限逼近對方的理想型。最可怕的是,還包郵呢親,關注一波瞭解一下?
上班第一天,老闆給了小K一份單身男女好感的資料資料。如下圖,連線表示雙方互有好感,可以嘗試處物件。
突然遇到了一個問題,那怎麼才能進行最大的匹配,創造整體人民最大的幸福感呢,當然也可以順便拿最多的中介費啦。
### 2 不要慫,就是幹
很多時候不是你比別人差,而是你執行力不夠,在猶豫中喪失機會。
大家就先行動起來吧。
快看,男1號選手在小K的鼓勵(慫恿)下,率先對女1號發起了進攻。在離失敗只有0.01公分的時候,他竟然奇蹟般的完成反殺,沒錯,他成功啦,這種高超的技巧,嫻熟的手法簡直如同教科書一般,值得在座的每個同學深入研究反覆琢磨啊。
男2號選手也不甘落後,也對女2號選手發起了進攻,沒錯,又一次成功啦。
男3號選手:我勒個去,我上我也行啊。於是也對自己心動的女1號發起了進攻,毫無意外,他陣亡了。。。
中間彩蛋。
男3號不甘心,原地復活,想再戰一回。在一個地方跌倒,咱們就換一個地方再跌。。。
於是對女2號發起了進攻。
幾經波折。
男3號終於也成為了有牽絆的男人,不論未來有多久,只在乎曾經擁有過。
男4一看:這也沒我啥事兒了啊。
以上的過程其實就是經典的**匈牙利演算法**,求解二分圖的最大匹配問題。
### 3 匈牙利演算法
**二分圖**
定義:設G=(V,E)是一個無向圖,頂點集V可分割為兩個互不相交的子集X,Y,並且圖中每條邊關聯的兩個頂點都分屬於這兩個互不相交的子集,兩個子集內的頂點不相鄰。
判斷是否為二分圖的充要條件:G至少有兩個頂點,且其所有迴路的長度均為偶數。
判斷方法:染色法
* 開始對任意一未染色的頂點染色
* 判斷其相鄰的頂點中,若未染色則將其染上和相鄰頂點不同的顏色;
* 若已經染色且顏色和相鄰頂點的顏色相同則說明不是二分圖,若顏色不同則繼續判斷
可用bfs或者dfs。
**匹配**
在二分圖G的子圖M中,M的邊集E中的任意兩條邊都不依附於同一個頂點,則稱M是一個**匹配**。
**飽和點**
匹配M的邊集所關聯的點為**飽和點**,否則為**非飽和點**。如上圖:
* $M_1$的飽和點:$X_1,X_3,X_4,Y_1,Y_2,Y_3$。
* $M_2$的飽和點:$X_1,X_2,Y_1,Y_3$。
**交錯路**
定義:圖G的一條路徑,且路徑中的邊在屬於M和不屬於M中交替出現。
**增廣路(非網路流中的定義)**
定義:一條交錯路,且該交錯路的起點和終點都為匹配M的非飽和點。
如上圖,交錯路1是增廣路;交錯路2不是增廣路,因為終點$$X_1$$不是非飽和點。
由增廣路推出以下結論:
* 路徑的邊數為奇數,第一條邊和最後一條邊都不屬於M
* 將路徑中的邊的匹配方式取反操作,會得到更大的匹配M',匹配數加1
* M為圖G的最大匹配等價於不存在M的增廣路
匈牙利演算法核心思想:
* 1.初始匹配M為空
* 2.找出一條增廣路徑p,取反操作得到更大的匹配M'代替M
* 3.重複步驟2,直到找不出增廣路為止
### 4 程式碼實現
**變數定義及初始化**
```cpp
const int MAXM = 200, MAXN = 200;
bool map[MAXN][MAXM] = {false}, visit[MAXM];
int n, m, x[MAXM], y[MAXN], ans = 0;
```
**初始化**
```cpp
void init() {
memset(x, 0xff, MAXM * 4);
memset(y, 0xff, MAXN * 4);
memset(map, false, MAXN * MAXM);
int num, temp;
cin >> n >> m;
for (int i = 0; i < n; ++i) {
cin >> num;
for (int j = 0; j < num; ++j) {
cin >> temp;
map[i][temp - 1] = true;
}
}
}
```
**遞迴尋找增廣路**
```cpp
bool hungary(int u) {
for (int i = 0; i < m; ++i) {
if (!visit[i] && map[u][i]) {
visit[i] = true;
if (y[i] == -1 || hungary(y[i])) {
x[u] = i;
y[i] = u;
return true;
}
}
}
return false;
}
```
**遍歷所有點**
```cpp
int main() {
init();
for (int i = 0; i < n; ++i) {
if (x[i] == -1) {
memset(visit, false, MAXM);
if (hungary(i)) {
ans++;
}
}
}
cout << ans << endl;
return 0;
}
```
**測試資料**
```cpp
輸入
5 5
2 2 5
3 2 3 4
2 1 5
3 1 2 5
1 2
輸出
4
```
---
**掃描下方二維碼關注公眾號,第一時間獲取更新資訊!**