设G=(V,{R})是一个无向图。如顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属两个不同的子集。则称图G为二分图。
给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
选择这样的边数最大的子集称为图的最大匹配问题(maximal matching problem)
如果一个匹配中,图中的每个顶点都和图中某条边相关联,则称此匹配为完全匹配,也称作完备匹配。
最大匹配在实际中有广泛的用处,求最大匹配的一种显而易见的算法是:先找出全部匹配,然后保留匹配数最多的。即回溯法,但是这个算法的复杂度为边数的指数级函数。因此,需要寻求一种更加高效的算法。
二分图的最大匹配有2种实现,网络流和匈牙利算法。
匈牙利算法是求解最大匹配的有效算法,该算法用到了增广路的定义(也称增广轨或交错轨):若边集合P是图G中一条连通两个未匹配顶点的路径,并且属M的边和不属M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。
由增广路径的定义可以推出下述三个结论:
1. P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
2. P经过“取反操作”(即非M中的边变为M中的边,原来M中的边去掉)可以得到一个更大的匹配M’。
3. M为G的最大匹配当且仅当不存在相对于M的增广路径。
从而可以得到求解最大匹配的匈牙利算法:
(1)置M为空
(2)找出一条增广路径P,通过“取反操作”获得更大的匹配M’代替M
(3)重复(2)操作直到找不出增广路径为止
根据该算法,可以选择深搜或者广搜实现,下面给出易于实现的深度优先搜索(DFS)实现。
//prototype
int n, m, match[100]; //二分图的两个集合分别含有n和m个元素,match[i]存储集合m中的节点i在集合n中的匹配节点,初值为-1。
bool visited[100], map[100][100]; //map存储邻接矩阵。
bool DFS(cosnt int &k)
{
for(int i = 0; i < m; i++)
if( map[k][i] && !visited[i] )
{
visited[i] = true;
if( match[i] == -1 || DFS(match[i]) ) //寻找是否为增广路径
{
match[i] = k; //路径取反操作。
return true;
}
}
return false;
}
int main(void)
{
//...........
int count = 0;
memset(match, -1, sizeof(match));
for(i = 0; i < n; i++)
{ //以二分集中的较小集为n进行匹配较优
memset(visited, 0,sizeof(visited));
if( DFS(i) ) ++count; //count为匹配数
}
//............
return 0;
}
优点:实现简洁,容易理解,适用于边比较多的图,DFS找增广路快.
相比较而言,广度优先搜索的优点:适用于稀疏二分图,边较少,增广路较短。(BFS实现略)
另外,二分图最大匹配可以转换成最大流问题来解。假设二分图的两个顶点集分别为X, Y,那么我们在图中添加一个源s,和一个汇t。同时,在s与X的每个顶点间加一条有向边(s, vx),在Y的每个顶点与t间加一条有向边(vy, t)。X与Y的每条边也变为X->Y的有向边。图中每条边的容量为1。则最大匹配问题就转换为求转换后的图G'的最大流的问题。利用Ford-Fulkerson方法求最大流,时间复杂度为O(VE), V为顶点数,E为边数。具体证明参考《算法导论》。但是,经典的求二分图最大匹配的算法是Edmond于1965年提出的匈牙利算法。
题目:
JOJ 1102 Courses http://acm.jlu.edu.cn/joj/showproblem.php?pid=1102 (最基本的二分图匹配)
JOJ 2393 The problem of a CEO http://acm.jlu.edu.cn/joj/showproblem.php?pid=2393(二分图匹配+字符串处理)