导读:
二分图指的是这样一种图:其所有的顶点分成两个集合M和N,其中M或N中任意两个在同一集合中的点都不相 连。二分图匹配是指求出一组边,其中的顶点分别在两个集合中,并且任意两条边都没有相同的顶点,这组边叫做二分图的匹配,而所能得到的最大的边的个数,叫 做最大匹配。
计算二分图的算法有网络流算法和匈牙利算法(目前就知道这两种),其中匈牙利算法是比较巧妙的,具体过程如下(转自组合数学):
令g=(x,*,y)是一个二分图,其中x={x1,x2...},y={y1,y2,....}.令m为g中的任意匹配。
1。将x的所有不与m的边关联的顶点表上¥,并称所有的顶点为未扫描的。转到2。
2。如果在上一步没有新的标记加到x的顶点上,则停,否则 ,转3
3。当存在x被标记但未被扫描的顶点时,选择一个被标记但未被扫描的x的顶点,比如xi,用(xi)标记y 的所有顶点,这些顶点被不属于m且尚未标记的边连到xi。现在顶点xi 是被扫描的。如果不存在被标记但未被扫描的顶点,转4。
4。如果在步骤3没有新的标记被标记到y的顶点上,则停,否则转5。
5。当存在y被标记但未被扫描的顶点时。选择y的一个被标记但未被扫描的顶点,比如yj,用(yj)标记x的顶点,这些顶点被属于m且尚未标记的边连到yj。现在,顶点yj是被扫描的。如果不存在被标记但未被扫描的顶点则转道2。由于每一个顶点最多被标记一次且由于每一个顶点最多被扫描一次,本匹配算法在有限步内终止。
代码实现:
#include<stdio.h> #include<string.h> #define MAX 100 bool map[MAX][MAX],searched[MAX]; int prev[MAX],m,n; bool dfs(int data) { int i,temp; for(i=0;i<m;i++) { if(map[data]&&!searched) { searched=true; temp=prev; prev=data; if(temp==-1||dfs(temp)) return true; prev=temp; } } return false; } main() { int num,i,k,temp1,temp2,job; while(scanf("%d",&n)!=EOF&&n!=0) { scanf("%d%d",&m,&k); memset(map,0,sizeof(map)); memset(prev,int(-1),sizeof(prev)); memset(searched,0,sizeof(searched)); for(i=0;i<k;i++) { scanf("%d%d%d",&job,&temp1,&temp2); if(temp1!=0&&temp2!=0) map[temp1][temp2]=true; } num=0; for(i=0;i<n;i++) { memset(searched,0,sizeof(searched)); dfs(i); } for(i=0;i<m;i++) { if(prev!=-1) num++; } printf("%d/n",num); } }
二分图匹配算法总结(phoenixinter)
一、二分图最大匹配
二分图最大匹配的经典匈牙利算法是由Edmonds在1965 年提出的,算法的核心就是根据一个初始匹配不停的找增广 路,直到没有增广路为止。
匈牙利算法的本质实际上和基于增广路特性的最大流算 法还是相似的,只需要注意两点:(一)每个X节点都最多 做一次增广路的起点;(二)如果一个Y节点已经匹配了, 那么增广路到这儿的时候唯一的路径是走到Y节点的匹配点 (可以回忆最大流算法中的后向边,这个时候后向边是可以 增流的)。
找增广路的时候既可以采用dfs也可以采用bfs,两者都 可以保证O(nm)的复杂度,因为每找一条增广路的复杂度是 O(m),而最多增广n次,dfs在实际实现中更加简短。
二、Hopcroft-Karp算法
SRbGa很早就介绍过这个算法,它可以做到O(sqrt(n)*e) 的时间复杂度,并且在实际使用中效果不错而且算法本身并 不复杂。
Hopcroft-Karp算法是Hopcroft和Karp在1972年提出的, 该算法的主要思想是在每次增广的时候不是找一条增广路而 是同时找几条点不相交的最短增广路,形成极大增广路集, 随后可以沿着这几条增广路同时进行增广。
可以证明在寻找增广路集的每一个阶段所寻找到的最短 增广路都具有相等的长度,并且随着算法的进行最短增广路 的长度是越来越长的,更进一步的分析可以证明最多只需要 增广ceil(sqrt(n))次就可以得到最大匹配(证明在这里略 去)。
因此现在的主要难度就是在O(e)的时间复杂度内找到极大最短增广路集,思路并不复杂,首先从所有X的未盖点进行 BFS,BFS之后对每个X节点和Y节点维护距离标号,如果Y节点 是未盖点那么就找到了一条最短增广路,BFS完之后就找到了 最短增广路集,随后可以直接用DFS对所有允许弧(dist[y]= dist[x]+1,可以参见高流推进HLPP的实现)进行类似于匈牙利中寻找增广路的操作,这样就可以做到O(m)的复杂度。
实现起来也并不复杂,对于两边各50000个点,200000 条边的二分图最大匹配可以在1s内出解,效果很好:)
三、二分图最优匹配
二分图最优匹配的经典算法是由Kuhn和Munkres独立提出的KM算法,值得一提的是最初的KM算法是在1955年和1957年提出的,因此当时的KM算法是以矩阵为基础的,随着匈牙利算法被Edmonds提出之后,现有的KM算法利用匈牙利树可以得到更漂亮的实现。
KM算法中的基本概念是可行顶标(feasible vertex labeling),它是节点的实函数并且对于任意弧(x,y)满足l(x)+l(y)>=w(x,y),此外一个概念是相等子图,它是G的一个生成子图,但是只包含满足l(xi)+l(yj)=w(xi,yj)的所有弧(xi,yj)。
有定理:如果相等子图有完美匹配,那么该匹配是最大权匹配,证明非常直观也非常简单,反设其他匹配是最优匹配,它的权必然比相等子图的完美匹配的权要小。
KM算法主要就是控制了怎样修改可行顶标的策略使得最终可以达到一个完美匹配,首先任意设置可行顶标(如每个X节点的可行顶标设为它出发的所有弧的最大权,Y节点的可行顶标设为0),然后在相等子图中寻找增广路,找到增广路就沿着增广路增广。
而如果没有找到增广路呢,那么就考虑所有现在在匈牙利树中的X节点(记为S集合),所有现在在匈牙利树中的Y节点(记为T集合),考察所有一段在S集合,一段在not T集合中的
弧,取 delta = mio {l(xi)+l(yj)-w(xi,yj),xi /in S, yj /in not T}
明显的,当我们把所有S集合中的l(xi)减少delta之后,一定会有至少一条属于(S,not T)的边进入相等子图,进而可以继续扩展匈牙利树,为了保证原来属于(S,T)的边不退出相等子图,把所有在T集合中的点的可行顶标增加delta。
随后匈牙利树继续扩展,如果新加入匈牙利树的Y节点是未盖点,那么找到增广路,否则把该节点的对应的X匹配点加入匈牙利树继续尝试增广。
复杂度分析:由于在不扩大匹配的情况下每次匈牙利树做 如上调整之后至少增加一个元素,因此最多执行n次就可以找到一条增广路,最多需要找n条增广路,故最多执行n^2次修改顶标的操作,而每次修改顶标需要扫描所有弧,这样修改顶标的复杂度就是O(n^2)的,总的复杂度是O(n^4)的。
事实上我现在看到的几个版本的实现都是这样实现的,但是实际效果还不错,因为这个界通常很难达到。
对于not T的每个元素yj,定义松弛变量slack(yj) = min{l(xi)+l(yj)-w(xi,yj),xi /in S},很明显的每次的delta =min{slack(yj),yj/in not T},每次增广之后用O(n^2)的时间计算所有点的初始slack,由于生长匈牙利树的时候每条弧的顶标增量相同,因此修改每个slack需要常数时间(注意在修改顶标后和把已盖Y节点对应的X节点加入匈牙利树的时候是需要修改slack的)。这样修改所有slack值时间是O(n)的,每次增广后最多修改n次顶标,那么修改顶标的总时间降为O(n^2),n次增广的总时间复杂度降为O(n^3)。事实上我这样实现之后对于大部分的数据可以比O(n^4)的算法快一倍左右。
四、二分图的相关性质
本部分内容主要来自于SRbGa的黑书,因为比较简单,仅作提示性叙述。
(1) 二分图的最大匹配数等于最小覆盖数,即求最少的点使得每条边都至少和其中的一个点相关联,很显然直接取最大匹配的一段节点即可。
(2) 二分图的独立数等于顶点数减去最大匹配数,很显然的把最大匹配两端的点都从顶点集中去掉这个时候剩余的点是独立集,这是|V|-2*|M|,同时必然可以从每条匹配边的两端取一个点加入独立集并且保持其独立集性质。
(3) DAG的最小路径覆盖,将每个点拆点后作最大匹配,结果为n-m,求具体路径的时候顺着匹配边走就可以,匹配边i->j??,j->k??,k->l??....构成一条有向路径。
五、稳定婚姻问题
稳定婚姻问题是一个很有意思的匹配问题,有n位男士和n位女士,每一个人都对每个异性有一个喜好度的排序,代表对他的喜爱程度,现在希望给每个男士找一个女士作配偶,使得每人恰好有一个异性配偶。如果男士u和女士v不是配偶但喜欢对方的程度都大于喜欢各自当前配偶的程度,则称他们为一个不稳定对。
稳定婚姻问题就是希望找出一个不包含不稳定对的方案。
算法非常简单,称为求婚-拒绝算法,每位男士按照自己喜欢程度从高到低依次给每位女士主动求婚直到有一个女士接受他,对于每个女士,如果当前向她求婚的配偶比她现有的配偶好则抛弃当前配偶,否则不予理睬,循环往复直到所有人都有配偶。有趣的是,看起来是女士更有选择权,但是实际上最后的结果是男士最优的(man-optimal)。
首先说明最后匹配的稳定性,随着算法的执行,每位女士的配偶越来越好,而每位男士的配偶越来越差。因此假设男士u和女士v形成不稳定对,u一定曾经向v求过婚,但被拒绝。这说明v当时的配偶比u更好,因此算法结束后的配偶一定仍比u好,和不稳定对的定义矛盾,类似的,方式我们考虑最后一个被抛弃的男士和抛弃这位男士的女士,不难得出这个算法一定终止的结论。
如果存在一个稳定匹配使得男士i和女士j配对,则称(i,j)是稳定对。对于每个男士i,设所有稳定对(i,j)中i 最喜欢的女士为best(i),则可以证明这里给出的算法对让每位男士i与best(i) 配对。对于所有男士来说,不会有比这更好的结果了,而对于女士则恰恰相反,对于她们来说不会有比这更糟的结果了,因此这个算法是男士最优的。
算法一定得到稳定匹配,并且复杂度显然是O(n^2),因为每个男士最多考虑每个女士一次,考虑的时间复杂度是O(1),当然了,需要作一定的预处理得到这个复杂度。