算法学习笔记(32)——匈牙利算法(二分图的最大匹配)

匈牙利算法

给定一个二分图,如:
img

匈牙利算法能够快速的计算出一种匹配方式,使得匹配的数量最多。注意,一个成功的匹配方式中,没有两条边是共用了同一个点的。

形象的说,这个问题可以理解成二分图两边分别是男生和女生,有连线的表示可以凑成一对,匈牙利算法就是用来计算最多能够凑成多少对(不存在脚踏多条船的情况)。

例如左边是男生,右边是女生,可以任选一方为主动方,比如是男生方,那么流程如下:

对于每个男生结点,对所有与之有连线的女生结点,检查对应的女生是不是单身,如果是就直接凑成一对。那么在上图的例子中,前两个男生都可以直接匹配到自己连线的第一个女生:
img

对于男生 3 而言,他所能匹配的第一个女生是 2 ,但是这个女生已经是非单身了。这个时候就要去不断尝试,尝试让女生 2 已经匹配的男生 1 换一个匹配的女生(找下家,而不会让当前已经成对的匹配数量减少)。

接下来男生 1 检查自己所能匹配的下一个女生 4 ,这个女生是非单身,所以将男 1 与女 4 匹配,此时女 2 被释放出来,得以和男 3 匹配:
img

接下来检查下一个男生 4 ,它所能匹配的女生 3 是单身,将他俩匹配:
img

至此,能匹配的都匹配上了,这个二分图的最大匹配数量是4。

题目链接:AcWing 861. 二分图的最大匹配

求最大匹配的时候,可以直接存到邻接表里,因为遍历的时候是单方向对每个男生遍历所有能匹配的女生,所以只要存一下从男生到女生的边,不需要像存普通无向图那样存双向边。

在实现时要注意,match[]数组用来记录每个女生当前匹配的男生是哪一个(存标号),如果单身里面存的就是0

由于在匈牙利算法中,即使一个女生已经有匹配了,也可能被更换匹配,所以还需要每次清空一个st[]数组,来记录当前给某个男生找匹配的过程中,每个女生有没有遍历过,防止出现死循环。

匈牙利算法基于贪心的思想,理论时间复杂度是多项式级别的 \(O(nm)\) ,但是实际运行速度还是比较快的。

#include <iostream>
#include <cstring>

using namespace std;

const int N = 510, M = 1e5 + 10;

int n1, n2, m;
int h[N], e[M], ne[M], idx; // 邻接表存图
int match[N];   // 记录每个女生匹配的男生
bool st[N];     // 标记当前女生是否被考虑过

// 邻接表加边操作
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

// 匈牙利算法find操作
bool find(int x)
{
    // 考虑x喜欢的每一个女生
    for (int i = h[x]; i != -1; i = ne[i]) {
        int j = e[i];
        // 如果这个女生还没有被考虑过
        if (!st[j]) {
            // 尝试让x与j匹配,先锁定j女生
            st[j] = true;
            // 如果当前女生还没有进行匹配 或者 可以对当前女生匹配的男生找到下家
            // 就将该女生与该男生匹配,返回成功
            if (match[j] == 0 || find(match[j])) {
                match[j] = x;
                return true;
            }
        }
    }
    
    // 考虑了所有女生后都没有匹配成功,返回失败
    return false;
}

int main()
{
    memset(h, -1, sizeof h);
    
    cin >> n1 >> n2 >> m;
    while (m -- ) {
        int u, v;
        cin >> u >> v;
        add(u, v);
    }
    
    // 用于记录成功匹配的对数
    int res = 0;
    // 对每一个男生进行匹配
    for (int i = 1; i <= n1; i ++ ) {
        // 每次都要从头考虑每个女生
        memset(st, false, sizeof st);
        // 如果可以成功给当前男生匹配,匹配成功数+1
        if (find(i)) res ++;
    }
    
    cout << res << endl;
    
    return 0;
}
posted @ 2022-12-10 09:33  S!no  阅读(127)  评论(0编辑  收藏  举报