二分图匹配与匈牙利算法
二分图匹配与匈牙利算法
二分图是一类特殊的无向图,其顶点可分为两类,使用集合 U 和 V 表示,对于任意一条边,其所连接的两个顶点一个来自 U 一个来自 V。
如:
U = { a, b, c, d, e}
V = { 1, 2, 3, 4, 5}
边集 {(a, 1), (b, 1), (c, 2), (c, 3), (d, 4), (e, 5)}
取边集的一个子集,使得子集中任意顶点最多出现一次,则为二分图的一个匹配,当子集包含的边数最多时,称为最大匹配。
求解最大匹配可使用匈牙利算法(发明者是匈牙利人),基本的流程如下:
对于 U 中的顶点 U[i],遍历 V:
(1) 如果 U[i], V[j] 有边,并且 V[j] 未有匹配(R[j] < 0),
那么 V[j] 对应的结果 R[j] 就为 i
(2) 如果 U[i], V[j] 有边,但是 V[j] 在之前已经有匹配结果 (R[j] >= 0),
这时有两种搜索方式:广度优先、深度优先
广度优先:先继续 j++ ,直到无法找到 V[j] 与 U[i] 有边,那么就需要尝试回溯之前的搜索,
调整 U[i-1] 对应的值
深度优先:直接调整 R[j] 对应的 U 点匹配,进行回溯
想完整了解匈牙利算法的流程,见 http://baike.baidu.com/item/%E5%8C%88%E7%89%99%E5%88%A9%E7%AE%97%E6%B3%95
深度优先的搜索会更好实现,代码如下:
bool g[M][N]; // 图,M N 分别为 U V 的大小
int result[N]; // V[i] 对应的 U 结果下标
bool state[N]; // V[i] 是否已经过搜索,如果在某次回溯前已搜索过,则不能再使用 i
// 深度优先搜索 U[a] 是否能找到对应点
bool dfs(int a)
{
for (int i=0; i<N; i++) {
// U[a] 与 V[i] 有边,且 V[i] 未有搜索过
if (g[a][i] && !state[i]) {
// V[i] 未有匹配结果,或者能进行回溯调整前面的结果
if (result[i] < 0 || dfs(result[i]) {
result[i] = a;
return true;
}
}
}
// 所有与 U[a] 有边的 V[i] 已有对应结果,且无法调整
return false;
}
int main()
{
/* 初始化代码 ... */
int ans = 0; // 最大匹配有多少条边
for (int i=0; i<n; i++)
result[i] = -1;
// dfs 求最大匹配
for (int i=0; i<M; i++) {
// 本次初始化搜索状态
memset(state, 0, sizeof(state);
if (dfs(i)) {
++ans;
}
}
printf("%d\n", ans);
}
附上一个需要稍微转换一下思路的二分图题目:素数伴侣 https://www.nowcoder.com/questionTerminal/b9eae162e02f4f928eac37d7699b352e