1. 程式人生 > >其它 >【演算法學習筆記】26:匈牙利演算法(二分圖最大匹配)

【演算法學習筆記】26:匈牙利演算法(二分圖最大匹配)


技術標籤:演算法(學習)匈牙利演算法二分圖二分圖匹配圖論

1 簡述

給定一個二分圖,例如:

【演算法學習筆記】26:匈牙利演算法(二分圖最大匹配)

匈牙利演算法能夠快速的計算出一種匹配方式,使得匹配的數量最多。注意,一個成功的匹配方式中,沒有兩條邊是共用了同一個點的。

形象的說,這個問題可以理解成二分圖兩邊分別是男生和女生,有連線的表示可以湊成一對,匈牙利演算法就是用來計算最多能夠湊成多少對(不存在腳踏多條船的情況)。


例如左邊是男生,右邊是女生,可以任選一方為主動方,比如是男生方,那麼流程如下。

對於每個男生結點,對所有與之有連線的女生結點,檢查對應的女生是不是單身,如果是就直接湊成一對。那麼在上圖的例子中,前兩個男生都可以直接匹配到自己連線的第一個女生:

【演算法學習筆記】26:匈牙利演算法(二分圖最大匹配)

對於男生 3 3 3而言,他所能匹配的第一個女生是 2 2 2,但是這個女生已經是非單身了。這個時候就要去不斷嘗試,嘗試讓女生 2 2 2已經匹配的男生 1 1 1換一個匹配的女生(而不會讓當前已經成對的匹配數量減少)。

接下來男生 1 1 1檢查自己所能匹配的下一個女生 4 4 4,這個女生是非單身,所以將男 1 1 1與女 4 4 4匹配,此時女 2 2 2被釋放出來,得以和男 3 3 3匹配:

【演算法學習筆記】26:匈牙利演算法(二分圖最大匹配)

接下來檢查下一個男生 4 4 4,它所能匹配的女生 3 3 3是單身,將他倆匹配:

【演算法學習筆記】26:匈牙利演算法(二分圖最大匹配)

至此,能匹配的都匹配上了,這個二分圖的最大匹配數量是4。

2 模板題:二分圖的最大匹配

求最大匹配的時候,可以直接存到鄰接表裡,因為遍歷的時候是對每個男生遍歷所有能匹配的女生,所以只要存一下從男生到女生的邊,不需要像存普通無向圖那樣存雙向邊。

在實現時要注意,int match[]陣列用來記錄每個女生當前匹配的男生是哪一個(存標號),如果單身裡面存的就是0。

由於在匈牙利演算法中,即使一個女生已經有匹配了,也可能被更換匹配,所以還需要每次清空一個bool st[]陣列,來記錄當前給某個男生找匹配的過程中,每個女生有沒有遍歷過,防止出現死迴圈。

#include <iostream>
#include <cstring>

using namespace std;

// 由於只存單項,邊M不用開兩倍
const int N = 510, M = 1e5 + 10;

// 鄰接表
int h[N], e[M], ne[M], idx;
void add_edge(int a, int b) {
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++ ;
}

// match記錄女生當前匹配的男生
int match[N];
// st記錄當前這輪每個女生有沒有遍歷過
// st[j] = true時候,j這個女生是被禁用的
bool st[N];
// 匈牙利演算法,嘗試給x找一個女朋友
bool find(int x) {
    // 對於能夠和x匹配的所有女生j
    for (int i = h[x]; i != -1; i = ne[i]) {
        int j = e[i];
        // 如果j沒有被禁用(沒有遍歷過)
        if (!st[j]) {
            // 就將其鎖定,因為要嘗試讓x匹配j
            st[j] = true;
            // 如果j本來就是單身,或者j的男朋友能換一個女朋友
            if (match[j] == 0 || find(match[j])) {
                // 就將x匹配上j
                match[j] = x;
                // 因為x能找到女朋友,所以返回true
                return true;
            }
        }
    }
    // 嘗試了x的所有能匹配的j都不行,就返回false
    return false;
}

int main() {
    // 讀取二分圖a->b
    memset(h, -1, sizeof h);
    int n1, n2, m;
    cin >> n1 >> n2 >> m;
    for (int i = 0; i < m; i ++ ) {
        int a, b;
        cin >> a >> b;
        add_edge(a, b);
    }
    // 遍歷每個男生嘗試匹配
    int res = 0;
    for (int i = 1; i <= n1; i ++ ) {
        memset(st, false, sizeof st);
        // 每次嘗試都會嘗試讓i匹配進來
        // 結果res是隻增不減的
        if (find(i)) res ++ ;
    }
    cout << res << endl;
    return 0;
}

特別要注意每輪要清空st陣列,在這一輪中,嘗試讓女生j匹配給男生x時,就要設定st[j] = true以將j鎖定了,被鎖定的女生j永遠不會解鎖。

匈牙利演算法基於貪心的思想,理論時間複雜度是多項式級別的,但是實際執行速度還是比較快的。