这篇文章上次修改于 365 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
ACM程序课算法笔记6——二分图匹配
前置定义
二分图(Bipartite Graph ):如果一个图的顶点可以分为两个集合X和Y,图的所有边连接的两个点都来自不同的集合,则称该图为“二分图”或“二部图”。
最大匹配问题
对于一个二分图,问最多可以有几对边连接的点,并且这些点不重复。
说的形象一点,类似于婚配问题,将所有人分为男女两组,告诉你哪些人互相匹配(A能接受B,B必定接受A),问最多可以配成几对。
这就是一个典型的最大匹配,对于结果我们成为最大匹配对数
匈牙利算法
通过最简单的思路来看,我们就从上往下进行判断
操作A(x)判断这位男性x可以匹配的对象中有没有未被匹配的。若有则匹配,并进行A(x的下一位):若无
判断x连接的已被匹配的女性y,y对应匹配男性为z,判断z是否可以取消该匹配获得新匹配(即操作A(z)),若能,则匹配,若不能,则x无法被匹配。
范例代码:
#include <bits/stdc++.h>
using namespace std;
int findMaximumMatching(const vector<vector<int>> & adj, const function<bool(int)> & onLeft){
int result = 0;
vector<int> linked(adj.size(), -1); // linked[i] 值为 j 意为对于右点i,其连接为j
vector<bool> visited(adj.size());
function<bool(int)> dfs = [&](int u){
for(int v:adj[u]){
if(visited[v]) continue;
visited[v] = true;
if(linked[v]==-1||dfs(linked[v])){
linked[v] = u;
return true;
}
}
return false;
};
for(int i = 0; i < adj.size(); i++){
if(not onLeft(i)) continue;
fill(visited.begin(), visited.end(), false);
if(dfs(i)) result++;
}
return result;
}
最小顶点覆盖
覆盖:当一个边连接到某个点,可以说为该边被该点覆盖
给一副二分图,问该图最少被几个点覆盖?
该结果我们称为最小顶点覆盖。对于最小顶点覆盖,不妨先寻找它和最大匹配对数的关系
证明:最大匹配对数 = 最小顶点覆盖
要证明两者相等,不妨先得出
- 最小顶点覆盖A >= 最大匹配对数B:
对于最大匹配,暂时去掉所有除了匹配边之外的边,剩余B条边,这B条边都由两个点覆盖,又因为所有的边都要被覆盖,这B条边只能被它的两端的某个点覆盖,这些点不会重复,所以为了覆盖这B条边,至少需要B个点,可得A >= B。
- 最小顶点覆盖A <= 最大匹配对数B
对于最小顶点覆盖的A个点,去掉其它的所有点,剩下的每个点都有一个特性——每个点的所有边中至少有一个边另一端是空的,利用反证法,若某个点的所有边中都有它的另一端,那么该点可以被删除而不被纳入最小顶点覆盖。得证。然后对于最小覆盖的每个点,它和另一端空的边必能形成一个匹配,故B >= A
根据以上得证!
最小定点覆盖题就被转化为最大匹配问题
DAG图的最小路径覆盖
用尽量少的不相交简单路径覆盖有向无环图(DAG) 的所有顶点,这就是——DAG图的最小路径覆盖问题。
路径覆盖问题的目标是选择尽可能少的路径,以覆盖图中的所有节点。这些路径不能相交,除非它们共享一个起点或终点。
对于图
graph LR
1((1))
2((2))
3((3))
4((4))
1 --> 3
2 --> 3
3 --> 4
可见只需选择点1, 2便可覆盖所有点
变化思考
graph TD
1((1))
2((2))
3((3))
4((4))
5((1'))
6((2'))
7((3'))
8((4'))
1 --> 7
2 --> 7
3 --> 8
由于是有向图,我们应当思考如何转化为无向图问题,对于有向,我们可以让每个点都获得它的复制体,并且总是复制体被指向,原来的点指出。
结论:
最小路径覆盖数=节点数(n)-最大匹配数(m)
证明:
一开始每个点都是一条路径,每次找一条匹配边,代表合并两条路径
由于路径不相交(即每个点的入度和出度至少有一个为1),所以二分图上的边也不相交(如果相交则说明某个点的入度或出度大于1),这正好是匹配的定义
每条匹配边代表答案减去1,所以最小路径覆盖=原图节点数-新图最大匹配数
int findMinimumPathCover(const vector<vector<int>> & adj){
int maximumMatching = 0;
int nodeNum = adj.size();
vector<int> linked(nodeNum, -1); // linked[i] 值为 j 意为对于右点i,其连接为j
vector<bool> visited(nodeNum);
function<bool(int)> dfs = [&](int u){
for(int v:adj[u]){
if(visited[v]) continue;
visited[v] = true;
if(linked[v]==-1 or dfs(linked[v])){
linked[v] = u;
return true;
}
}
return false;
};
for(int i = 0; i < adj.size(); i++){
fill(visited.begin(), visited.end(), false);
if(dfs(i)) maximumMatching++;
}
return nodeNum - maximumMatching;
}
最大独立集
二分图的最大独立集是指在一个二分图中选择最大数量的节点,使得这些节点之间没有直接相连的边。
-
独立集: 一个图的独立集是指图中的一组节点,其中任意两个节点之间没有直接相连的边。
-
最大独立集: 对于给定的图,最大独立集是指具有最大节点数的独立集。
结论:
二分图最大独立集 = 节点数(n) - 二分图最大匹配
证明:
去掉二分图的最小点覆盖,剩下的点就是一个独立集,添加任意一个最小点覆盖中的点,依据之前的定理:最小点覆盖的每个点都有一个特性——每个点的所有边中至少有一个边另一端是空的,然而这个空的点就属于我们剩下的独立集,也就是说,添加任意一个最小点覆盖中的点,都会导致生成一条边,故:二分图最大独立集 = 节点数(n) - 二分图最小点覆盖 = 节点数(n) - 二分图最大匹配