二分图匹配(匈牙利算法)
💡 核心思想
匈牙利算法用于求解二分图的最大匹配问题。核心思想是"增广路":如果当前点未匹配,则尝试匹配;如果已匹配,则尝试让已匹配的点换个对象,腾出位置给当前点。通过 DFS 寻找增广路,每找到一条增广路,匹配数就加 1。
匈牙利算法是二分图匹配的入门算法,代码短、好理解。NOIP 提高组几乎每年都有一道二分图匹配的题。关键是理解"增广路"的概念:一条从未匹配点出发,经过未匹配边→匹配边→未匹配边...交替的路径,最后到达另一个未匹配点。把这条路径上的匹配边和未匹配边互换,匹配数就增加了 1。
🎯 直觉理解
想象你在组织一场联谊活动,左边是男生,右边是女生,边表示两人互相有好感。匈牙利算法就像一个"热心红娘":遇到一个男生,先给他介绍一个女生;如果这个女生已经有对象了,就去找那个女生的对象,问他能不能换个女生;如果能换,就皆大欢喜;如果不能,就继续找人换...直到所有人都尽力了。
📝 算法流程
- 初始化 match[] 数组为 0(未匹配)
- 对于每个左侧节点 u:
- 清空 vis 标记
- DFS 寻找增广路:遍历 u 的所有邻接点 v
- 如果 v 未访问且(v 未匹配 或 match[v] 能找到新的匹配)
- 则 match[v] = u,返回 true
- 如果找不到增广路,u 暂时无法匹配
- 答案为成功匹配的对数
$$\text{最大匹配数} = \text{最小点覆盖数} = |V| - \text{最大独立集数} \quad \text{(König 定理)}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(n \cdot m)$(n 为左侧点数,m 为边数) |
| 空间 | $O(n + m)$ |
💻 参考实现(C++)
C++ (C++17)
#include
using namespace std;
const int N = 505;
vector g[N]; // 左侧点 u 的邻接表(右侧点 v)
int match[N]; // 右侧点 v 匹配的左侧点
bool vis[N];
bool dfs(int u) {
for (int v : g[u]) {
if (vis[v]) continue;
vis[v] = true;
if (match[v] == 0 || dfs(match[v])) {
match[v] = u;
return true;
}
}
return false;
}
int hungarian(int nLeft) {
int res = 0;
for (int u = 1; u <= nLeft; u++) {
memset(vis, 0, sizeof(vis));
if (dfs(u)) res++;
}
return res;
}
int main() {
int n = 3, m = 3;
g[1].push_back(1); g[1].push_back(2);
g[2].push_back(2); g[2].push_back(3);
g[3].push_back(1); g[3].push_back(3);
cout << hungarian(n) << endl; // 3
return 0;
} ⚠️ 常见坑点
vis 数组没有清空——每次 DFS 前要重置
match 数组下标混淆——match[v] 记录的是左侧点
二分图判断错误——不是二分图不能用匈牙利
与网络流混淆——匈牙利是 O(nm),网络流更高效但代码更长
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P3386 二分图匹配 | 洛谷 | CSP-S | 匈牙利算法模板题 |
| P2756 飞行员配对 | 洛谷 | CSP-S | 二分图匹配应用 |
| P1129 矩阵游戏 | 洛谷 | CSP-S | 二分图匹配 + 行列变换 |