二分图:

定义:如果一个图的所有顶点可以被分为X和Y两个集合,并且所有边的两个顶点恰好一个属于集合X,另一个属于集合Y,即每个集合内的顶点没有边相连,那么此图就是二分图。

很多问题都可以转化为二分图匹配模型来计算。二分图有如下几种常见变形:

(1)最小顶点覆盖

选取最少的点(X或Y中都行),让每条边都至少和其中一个点关联。

Knoig定理:二分图的最小顶点覆盖数等于二分图的最大匹配数。

(2)最小路径覆盖

对于一个DAG(有向无环图),选取最少条路径,使得每个顶点属于且仅属于一条路径。

结论:DAG图的最小路径覆盖数 = 节点数(n) - 最大匹配数(m).

(3)最大独立集

选取最多的点,使任意所选两点均不相连。

结论:二分图的最大独立集数 = 节点数(n) - 最大匹配数(m).

匈牙利算法

匈牙利算法就是一种用增广路径求二分图最大匹配的算法,核心是寻找增广路径。

算法的时间复杂度为O(NM),其中N为二分图左边的顶点数,M为二分图中边的数目。

基本步骤:

1.首先从任意一个未被配对的点 u开始,从点u的边中任意选一条边(假设这条边是u->v)开始配对。如果此时点V还没有被配对,则配对成功,此时便找到了一条增广路(只不过这条增广路比较简单)。如果此时点v已经被配对了,那就要尝试进行“连锁反应”。如果尝试成功了,则找到一条增广路,此时需要更新原来的配对关系。这里要用一个数组match来记录配对关系,比如点v与点u配对了,就记作match[v]=u。配对成功后,记得要将配对数加1。配对的过程我们可以通过深度优先搜索来实现,当然广度优先搜索也可以。

2.如果刚才所选的边配对失败,要从点u的边中再重新选一条边, 进行尝试。直到点u配对成功,或者尝试过点u所有的边为止。

3.接下来继续对剩下没有被配对的点一一进行配对, 直到所有的点都尝试完毕,找不到新的增广路为止。

核心代码:

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++)
        vis[j] = 0; //清空上次搜索时的标记
    if (dfs(i))
        sum++; //寻找增广路,如果找到,配对数加1
}
int dfs(int u) {
    for (int i = 1; i <= n; i++) {
        if (!vis[i] && map[u][i]) {
            vis[i] = 1; //标记点i已经访问过
            //如果点i未被配对或者找到了新的配对
            if (!match[i] || dfs(match[i])) {
                //更新配对关系
                match[i] = u;
                return 1;
            }
        }
    }
    return 0;
}