求二分图最大匹配——Hopcroft-Krap算法

本文是对二分图大讲堂这篇文章中Hopcroft-Krap算法代码实现的详细注释。

HK算法的基本原理

Hopcroft-Karp算法先使用BFS查找多条增广路,然后使用DFS遍历增广路(累加匹配数,修改匹配点集),循环执行,直到没有增广路为止。
Hopcroft-Karp算法的BFS遍历只对点进行分层(不标记是匹配点和未匹配点),然后用DFS遍历看上面的层次哪些是增广路径(最后一个点是未匹配的)。
BFS过程可以看做是图像树结构一样逐层向下遍历,还要防止出现相交的增广路径。

二分图大讲堂中给出了HK算法步骤的通俗解释

设U和V是图G的二分图,M是从U到V的匹配
(1)使用BFS遍历对图的点进行分层,从X中找出一个未匹配点v,(所有v)组成第一层,接下的层是这样形成的——都是查找匹配点(增广路性质),直到在V中找到未匹配点才终止查找,对X其他未匹配点同样进行查找增广路径(BFS只分层不标记是否匹配点)
(2)使用DFS遍历查找(1)形成的增广路,找到就匹配数就累加1
(3)重复(1)(2)操作直到找不出增广路径为止

二分图最大匹配之Hopcroft-Karp算法里给了比较学术化的步骤,有兴趣可以看一下

代码注解如下

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 1e5 + 10;
const int INF = 0x3f3f3f3f;

bool flag;
int p, n;
// 两个集合: X和Y
// Mx,My记录结点的匹配顶点
int Mx[MAXN], My[MAXN], Nx, Ny;
// dx,dy是结点的BFS遍历层次
int dx[MAXN], dy[MAXN], dis;
bool vst[MAXN], g[110][310];

bool searchP()
{
    queue<int> Q;
    dis = INF;
    memset(dx, 0x3f, sizeof dx);
    memset(dy, 0x3f, sizeof dy);
    // 使用BFS遍历对图的点进行分层,从X中找出一个未匹配点v
	// (所有v)组成第一层,接下来的层都是这样形成——每次查找
	// 匹配点(增广路性质),直到在Y中找到未匹配点才停止查找,
	// 对X其他未匹配点同样进行查找增广路径(BFS只分层不标记
	// 是否匹配点)
	// 找出X中的所有未匹配点组成BFS的第一层
    for (int i = 1; i <= Nx; ++i)
    {
        if (Mx[i] == -1)
        {
            Q.push(i);
            dx[i] = 0;
        }
    }
    while (!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        // 该路径长度大于dis,等待下一次BFS扩充
        // dis是到Y集合的长度,所以dis一定是一个奇数
        if (dx[u] > dis) break;
        for (int v = 1; v <= Ny; ++v)
        {
			// (u,v)之间有边且v还没有分层
			// 分配v的层次
            if (g[u][v] && dy[v] == -1)
            {
                dy[v] = dx[u] + 1;
                // v是未匹配点,停止延伸(查找)
                // 得到本次BFS的最大遍历层次
                if (My[v] == -1) dis = dy[v];
                // v是匹配点,继续延伸
                else
                {
                    dx[My[v]] = dy[v] + 1;
                    Q.push(My[v]);
                }
            }
        }
    }
    // 若dis为INF说明Y中没有未匹配点,也就是没有增广路径了
    return dis != INF;
}

// 用DFS遍历查找BFS形成的增广路,如果可以找到增广路就停止遍历并返回true
bool DFS(int u)
{
	for (int v = 1; v <= Ny; ++v)
    {
        if (!vst[v] && g[u][v] && dy[v] == dx[u] + 1)
        {
            vst[v] = 1;
            // 层次(也就是增广路径的长度)大于本次查找的dis
            // 是searchP中被break的情况,也就是还不确定是否是增广路
            // 只有等再次调用searchP再判断
            if (My[v] != -1 && dy[v] == dis) continue;
            // 是增广路径,更新匹配集
            if (My[v] == -1 || DFS(My[v]))
            {
                My[v] = u;
                Mx[u] = v;
                return true;
            }
        }
    }
    return false;
}

// 查找二分图的最大匹配
int MaxMatch()
{
    int res = 0;
    // 每个结点的匹配顶点置空
    memset(Mx, 0x3f, sizeof Mx);
    memset(My, 0x3f, sizeof My);
    // 如果BFS可以找到增广路径
    while (searchP())
    {
        memset(vst, 0 , sizeof vst);
        for (int i = 1; i <= Nx; ++i)
        {
            // 用DFS查找增广路径,增广路径一定从未匹配点开始
            // 如果查找到一个增广路径,匹配数加一
            if (Mx[i] == -1 && DFS(i))
                ++res;
        }
    }
    return res;
}
posted @ 2018-08-03 19:00  LuSimon  阅读(918)  评论(0编辑  收藏  举报