二分图:
定义:如果一个图的所有顶点可以被分为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;
}