算法学习笔记(32)——匈牙利算法(二分图的最大匹配)
匈牙利算法
给定一个二分图,如:
匈牙利算法能够快速的计算出一种匹配方式,使得匹配的数量最多。注意,一个成功的匹配方式中,没有两条边是共用了同一个点的。
形象的说,这个问题可以理解成二分图两边分别是男生和女生,有连线的表示可以凑成一对,匈牙利算法就是用来计算最多能够凑成多少对(不存在脚踏多条船的情况)。
例如左边是男生,右边是女生,可以任选一方为主动方,比如是男生方,那么流程如下:
对于每个男生结点,对所有与之有连线的女生结点,检查对应的女生是不是单身,如果是就直接凑成一对。那么在上图的例子中,前两个男生都可以直接匹配到自己连线的第一个女生:
对于男生 3 而言,他所能匹配的第一个女生是 2 ,但是这个女生已经是非单身了。这个时候就要去不断尝试,尝试让女生 2 已经匹配的男生 1 换一个匹配的女生(找下家,而不会让当前已经成对的匹配数量减少)。
接下来男生 1 检查自己所能匹配的下一个女生 4 ,这个女生是非单身,所以将男 1 与女 4 匹配,此时女 2 被释放出来,得以和男 3 匹配:
接下来检查下一个男生 4 ,它所能匹配的女生 3 是单身,将他俩匹配:
至此,能匹配的都匹配上了,这个二分图的最大匹配数量是4。
求最大匹配的时候,可以直接存到邻接表里,因为遍历的时候是单方向对每个男生遍历所有能匹配的女生,所以只要存一下从男生到女生的边,不需要像存普通无向图那样存双向边。
在实现时要注意,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;
}