导读:
二分图指的是这样一种图:其所有的顶点分成两个集合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。
由于每一个顶点最多被标记一次且由于每一个顶点最多被扫描一次,本匹配算法在有限步内终止。
代码实现:
bfs过程:
#include<stdio.h>
#include<string.h>
main()
{
bool map[100][300];
int i,i1,i2,num,num1,que[300],cou,stu,match1[100],match2[300],pque,p1,now,prev[300],n;
scanf("%25d",&n);
for(i=0;i {
scanf("%25d%25d",&cou,&stu);
memset(map,0,sizeof(map));
for(i1=0;i1 {
scanf("%25d",&num);
for(i2=0;i2 {
scanf("%25d",&num1);
map[i1][num1-1]=true;
}
}
num=0;
memset(match1,int(-1),sizeof(match1));
memset(match2,int(-1),sizeof(match2));
for(i1=0;i1 {
p1=0;
pque=0;
for(i2=0;i2 {
if(map[i1][i2])
{
prev[i2]=-1;
que[pque++]=i2;
}
else
prev[i2]=-2;
}
while(p1 {
now=que[p1];
if(match2[now]==-1)
break;
p1++;
for(i2=0;i2 {
if(prev[i2]==-2&&map[match2[now]][i2])
{
prev[i2]=now;
que[pque++]=i2;
}
}
}
if(p1==pque)
continue;
while(prev[now]>=0)
{
match1[match2[prev[now]]]=now;
match2[now]=match2[prev[now]];
now=prev[now];
}
match2[now]=i1;
match1[i1]=now;
num++;
}
if(num==cou)
printf("YES/n");
else
printf("NO/n");
}
}
dfs实现过程:
#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 {
if(map[data][i]&&!searched[i])
{
searched[i]=true;
temp=prev[i];
prev[i]=data;
if(temp==-1||dfs(temp))
return true;
prev[i]=temp;
}
}
return false;
}
main()
{
int num,i,k,temp1,temp2,job;
while(scanf("%25d",&n)!=EOF&&n!=0)
{
scanf("%25d%25d",&m,&k);
memset(map,0,sizeof(map));
memset(prev,int(-1),sizeof(prev));
memset(searched,0,sizeof(searched));
for(i=0;i {
scanf("%25d%25d%25d",&job,&temp1,&temp2);
if(temp1!=0&&temp2!=0)
map[temp1][temp2]=true;
}
num=0;
for(i=0;i {
memset(searched,0,sizeof(searched));
dfs(i);
}
for(i=0;i {
if(prev[i]!=-1)
num++;
}
printf("%25d/n",num);
}
}
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=813621
本文转自
http://blog.csdn.net/yyaadet/archive/2006/06/19/813621.aspx
文章2
二分图匹配算法总结(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),当然
了,需要作一定的预处理得到这个复杂度。
本文转自