二分图匹配与匈牙利算法

二分图匹配与匈牙利算法

二分图是一类特殊的无向图,其顶点可分为两类,使用集合 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

posted @ 2017-05-31 21:26  drop *  阅读(187)  评论(0编辑  收藏  举报