匹配
对于图G=(V,E)而言,它的匹配是指由这样的边构成的集合:任意两条边没有公共顶点。如果一个节点是匹配中某条边的顶点,则称这个顶点已匹配。
极大匹配:如果一个匹配不是任何匹配的真子图,那么它就是极大匹配。如果M是G的极大匹配,那么G中任意一条边和M必然至少有一个公共顶点。
最大匹配:包含了尽可能多的边的匹配。注:每个最大匹配都是极大匹配,但是每个极大匹配不一定是最大匹配。
完美(完备、完全)匹配:覆盖了图G所有节点的匹配。每个完美匹配都是最大匹配,因而也是极大匹配。完美匹配也是最小边覆盖(用最少的边包含图所有顶点),因此最大匹配所需边的数量不大于最小边覆盖中边的数量。
交替路径:路径中的边交替的属于匹配M和不属于匹配M。例如:若边1、3、5属于M,则边2、4、6不属于M。
增广路径:将起始节点和结束节点都未匹配的交替路径称为增广路径。
二分图和匈牙利算法
二分图:如果图G的所有节点能够分为两个不相交集合U和V,并且G中每条边连接U与V中节点,即集合U和V中所包含的节点与自身所在集合中节点无边相连。二分图不包含奇数长度的环。
二分图判定方法:判断一个图是否是二分图,采用深度优先遍历算法时,时间复杂度是线性的。主要思路是在深度优先搜索树中为每个节点分配一个区别于它的父节点的颜色,这样就能够生成一个包含了所有连接节点和它的父节点的边的双色最小生成树。但是对于非最小生成树的边,也许这种着色并不适合。因此,遍历剩余的非最小生成树的边,判断每条边的着色是否不同。如果包含了连接相同颜色节点的边,则该图不是二分图;若不包含,则该图满足二分图的条件。
匈牙利算法是一个组合优化算法,能够在多项式时间内解决任务分配问题。它是由Harold Kuhn在1955年提出的。
求最大匹配显而易见的算法是:先找出全部的匹配,然后保留匹配数最多的。但此时,算法的时间复杂度是指数级的。因此,匈牙利算法是相比之下更为高效的算法。
根据增广路径的定义,增广路径具有一下特点:
-
增广路径的长度必定为奇数,第一条边和最后一条边都不属于M;
-
将M和增广路径进行异或操作(去同存异),可以得到更大的匹配M’;
-
M为G的最大匹配当且仅当不存在M的增广路径。
算法轮廓
-
置M为空;
-
找出一条增广路径p,通过异或操作获得更大的匹配M’代替M;
-
重复2中操作直到找不出增广路径为止。
算法的时间复杂度:邻接矩阵 O(n3 邻接表 O(mn)
算法的空间复杂度:邻接矩阵 O(n2) 邻接表 O(m+n)
算法伪码
bool kuhn(vertex v) { if(used[v]) return false; //如果节点已被遍历过,返回 used[v] = true; //标记已经被遍历 for(vertex q adjacent to v) { if((q has no pair) or //如果q未在之前的匹配中(只有不在之前匹配中才能跳出循环), kuhn(pairs[q])) { //或者q在之前的匹配中,但是q相邻节点出发有增广路径 pairs[q] = v; return true; } } } find-max-matching { for(vertex v = {1...n}) { used = {0}; //清空上个节点的标记 kuhn(v); //从节点v尝试扩展 } }
注:算法中对于二分图G(U,V,E)中,U中节点分别做增广路径起点v进行遍历,非是全部节点。
算法思想
算法的主要思想就是将满足匹配条件的边和其余的边交错形成一条路径,在这条路径的基础上,将已经匹配的边和未匹配的边“反色”,这样得到的匹配仍然合法,匹配数能增加一对边。重复这个操作,可以证明,当不能再找到增广路径时就能够得到一个最大匹配。注:显然,连接两个未匹配节点的边也是交替路径。
该算法就好比走“迷宫”,取一个点v必然是未走过的点,若这个点的邻接点有增广路径,则走过已有交替路径这段迷宫后,看是否存在出口(同样未遍历的点),如果存在,则对于pairs[q]重新赋值,使得指向它的父节点,这样便实现了“变色”的过程。
参考文章:
[1]维基百科 Matching、Bipartite graph、Hungarian algorithm)、匈牙利算法。
[2]Matrix67