您的位置:首页 > 其它

二分图的最大匹配

2012-08-11 09:49 183 查看
二分图:图G中顶点集V可以分成互不相交的子集(X,Y),并且图中的每一条边所关联的点分别属于两个不同的顶点集,则图G叫二分图,如图所示:


二分图的匹配:给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配,当匹配数达到最大时为二分图的最大匹配。

当然最大匹配的方案可能不能,但是最终结果都是一样。上图中最大匹配可以由(1,1),(2,2),(3,3),(4,4)构成,也可以是:(1,1),(2,2),(3,3),(5,4)。

上图中的最大匹配数为4,这是其中的一种情况:(1,1),(2,2),(3,3),(4,4)。

增广路:若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。

匈牙利算法(转自:飘过的小牛的博客):

算法的核心是:不断寻找增广路,直到找不到增广路为止。


(图1)

(图2)

图1、给出两个匹配(1,5),(2,6)。图2为在这个基础上找到一条增广路:3->6->2->5->1->4。

增广路的性质:

(1)有奇数条边。

(2)起点在二分图的左半边,终点在右半边。

(3)路径上的点一定是一个在左半边,一个在右半边,交替出现。

(4)整条路径上没有重复的点。

(5)起点和终点都是目前还没有配对的点,而其它所有点都是已经配好对的。(如图1、图2所示,(1,5)和(2,6)在图1中是两对已经配好对的点;而起点3和终点4目前还没有与其它点配对.)

(6)路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。(如图1、图2所示,原有的匹配是(1,5)和(2,6),这两条配匹的边在图2给出的增广路径中分边是第2和第4条边。而增广路径的第1、3、5条边都没有出现在图1给出的匹配中。)

(7)把增广路径上的所有第奇数条边加入到原匹配中去,并把增广路径中的所有第偶数条边从原匹配中删除(这个操作称为增广路径的取反),则新的匹配数就比原匹配数增加了1个。(如图2所示,新的匹配就是所有蓝色的边,而所有红色的边则从原匹配中删除。则新的匹配数为3。)

========================================================================================

匈牙利算法的步骤:

⑴置二分图的最大匹配M为空;

⑵找出一条增广路径P,通过异或操作获得更大的匹配M’代替M;

⑶重复⑵操作直到找不出增广路径为止。

============================================================================

所以上图中寻找最大匹配过程可能如下:

(1)在最初始时,还没有任何匹配时,图1中的两条灰色的边本身也是增广路径。

(2)找到增广路径1->5,把它取反,则匹配数增加到1。

(3)找到增广路径2->6,把它取反,则匹配数增加到2。

(4)找到增广路径3->6->2->5->1->4,把它取反,则匹配数增加到3。

(5)再也找不到增广路径,结束。

要完成匈牙利算法,还需要一个重要的定理:

如果从一个点A出发,没有找到增广路径,那么无论再从别的点出发找到多少增广路径来改变现在的匹配,从A出发都永远找不到增广路径。

算法流程:

初始化二分图的最大匹配为空
for 二分图左半部分的每个点i
if(从点i出发如果能找到增广路) 取反,即最大匹配+1

(1)、邻接矩阵,数据量大一点可能会超时,其中n,m为二分图两个集合的顶点编号,具体模板:

int Path(int u) //Path寻找增广路
{   for(int i=1;i<=m;i++) //map记录两个顶点是否相连,即邻接矩阵,相连为1,否则为0
if(!used[i]&&map[u][i]) //如果这个点没有被访问,且与左半部分的点u相连
{   used[i]=1; //标记这个点已经被访问
if(Link[i]<0||Path(Link[i])) //如果点i没有匹配,或i已经匹配,但是从Link[i]找到新的匹配
{   Link[i]=u; //i和u匹配
return 1; //找到增广路
}
}
return 0;
}
int Max() //二分图的最大匹配数
{   int sum=0;  //初始化最大匹配为空
CLR(Link,-1); //link[x]记录当前与y节点相连的x的节点。
for(int i=1;i<=n;i++) //枚举二分图左半部分的每个点
{   CLR(used,0);
if(Path(i)) sum++; //能够找到增广路,则取反,匹配数+1
}
return sum;
}

(2)、邻接表+二分图的最大匹配模板:

struct ArcNode{
void Add(int u,int v)
{   next[num]=prior[u];
data[num]=v;
prior[u]=num++;
}
void Init()
{ CLR(prior,-1);num=0;}
int prior[MAX],next[MAX2],data[MAX2],num;
}A;
int Path(int u)
{   for(int i=A.prior[u];i!=-1;i=A.next[i])
if(!used[A.data[i]])
{   used[A.data[i]]=1;
if(Link[A.data[i]]<0||Path(Link[A.data[i]]))
{   Link[A.data[i]]=u;
return 1;
}
}
return 0;
}
int Max()
{   int sum=0;
CLR(Link,-1);
for(int i=1;i<=n;i++)
{   CLR(used,0);
if(Path(i)) sum++;
}
return sum;
}

例题1:NYOJ 239(月老的难题),一看就知道二分图匹配问题,用邻接矩阵超时N次。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAX=501;
const int MAX2=10010;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,used[MAX],Link[MAX];
struct ArcNode{ void Add(int u,int v) { next[num]=prior[u]; data[num]=v; prior[u]=num++; } void Init() { CLR(prior,-1);num=0;} int prior[MAX],next[MAX2],data[MAX2],num; }A; int Path(int u) { for(int i=A.prior[u];i!=-1;i=A.next[i]) if(!used[A.data[i]]) { used[A.data[i]]=1; if(Link[A.data[i]]<0||Path(Link[A.data[i]])) { Link[A.data[i]]=u; return 1; } } return 0; } int Max() { int sum=0; CLR(Link,-1); for(int i=1;i<=n;i++) { CLR(used,0); if(Path(i)) sum++; } return sum; }
int main()
{ int m,u,v,Case;
scanf("%d",&Case);
while(Case--)
{ scanf("%d%d",&n,&m);
A.Init();
for(int i=0;i<m;i++)
{ scanf("%d%d",&u,&v);
A.Add(u,v);
}
printf("%d\n",Max());
}
return 0;
}

题2:Tyvj 1035(棋盘分割),二分图的最大匹配。这个关键是建图。可以把棋盘染成两个部分,黑白相间,用1表示黑,0表示白,如图当棋盘大小为4*4的矩阵时:



用map[][]保存这个图形,这个时候我们可以将格子标号为1的作为一个集合,为0的做一个集合,这样就形成了一个二分图,那么我们只要求这个二分图的最大匹配即可。由于是用一个1*2的多米诺骨牌相覆盖,那么需要满足两个格子之间要相邻,且格子不能超出范围,另外题目给了你一些限制条件要排除被挖去的格子,难点就是怎么判断两个格子相邻,如果相邻那么就构成了二分图的一条边。我们可以将每个格子编上号(编号为:i*n+j---i为行j为列,如上图第一个格子编号为0,水平过来为1,2,3,第二行为4,5,6,7....最后一个格子编号为3*4+3=15),所以这个时候可以建图为:

for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
for(int k=0;k<4;k++)
{   int x=i+dx[k];
int y=j+dy[k];
if(Inside(x,y)&&map[i][j]+map[x][y]==1)
A.Add(i*n+j,x*n+y); //表示i*n+j这个格子和x*n+y这个格子相连
}

对了,这种做法要用邻接表优化,否则会超内存,其余的就是模板了。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=110;
const int MAX=100001;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,m,map

,used[MAX],Link[MAX];
int dx[4]={0,1,0,-1},dy[4]={-1,0,1,0};
bool Inside(int x,int y)
{   return x>=0&&x<n&&y>=0&&y<n;
}
struct ArcNode{
void Add(int u,int v)
{   next[num]=prior[u];
data[num]=v;
prior[u]=num++;
}
void Init()
{ CLR(prior,-1);num=0;}
int prior[MAX],next[MAX],data[MAX],num;
}A;
int Path(int u)
{   for(int i=A.prior[u];i!=-1;i=A.next[i])
if(!used[A.data[i]])
{   used[A.data[i]]=1;
if(Link[A.data[i]]<0||Path(Link[A.data[i]]))
{   Link[A.data[i]]=u;
return 1;
}
}
return 0;
}
int Max()
{   int sum=0;
CLR(Link,-1);
for(int i=0;i<n*n;i++)
{   CLR(used,0);
if(Path(i)) sum++;
}
return sum;
}
int main()
{   int u,v;
A.Init();
scanf("%d%d",&n,&m);
map[0][0]=1;
for(int i=1;i<n;i++)
map[0][i]=!map[0][i-1];
for(int i=1;i<n;i++)
for(int j=0;j<n;j++)
map[i][j]=!map[i-1][j];
for(int i=0;i<m;i++)
{   scanf("%d%d",&u,&v);
map[u-1][v-1]=-1;
}
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
for(int k=0;k<4;k++)
{   int x=i+dx[k];
int y=j+dy[k];
if(Inside(x,y)&&map[i][j]+map[x][y]==1)
A.Add(i*n+j,x*n+y);
}
printf("%d\n",Max()/2);
return 0;
}

1、最小顶点覆盖:

Konig定理:最小顶点覆盖=二分图的最大匹配。

POJ 1325(机器调度),记住起始点是从0开始的,在0状态下完成的不能算入到切换次数,即构图时:u*v!=0。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAX=110;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,m,map[MAX][MAX],used[MAX],Link[MAX];
int Path(int u)
{   for(int i=1;i<m;i++)
if(!used[i]&&map[u][i])
{   used[i]=1;
if(Link[i]<0||Path(Link[i]))
{   Link[i]=u;
return 1;
}
}
return 0;
}
int Max()
{   int sum=0;
CLR(Link,-1);
for(int i=1;i<n;i++)
{   CLR(used,0);
if(Path(i)) sum++;
}
return sum;
}
int main()
{   int u,v,num,flag;
while(scanf("%d",&n),n)
{   scanf("%d%d",&m,&num);
CLR(map,0);
for(int i=0;i<num;i++)
{   scanf("%d%d%d",&flag,&u,&v);
if(u*v) map[u][v]=1;
}
printf("%d\n",Max());
}
return 0;
}

2、有向无环图的最小路径覆盖=顶点数-最大匹配。

POJ 2060(Taxi Cab Scheme)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int MAX=510;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,map[MAX][MAX],Link[MAX],used[MAX];
struct Point{
int x,y,a,b;
int Start;
int End;
}T[MAX];
int Path(int u)
{   for(int i=1;i<=n;i++)
if(!used[i]&&map[u][i])
{   used[i]=1;
if(Link[i]<0||Path(Link[i]))
{   Link[i]=u;
return 1;
}
}
return 0;
}
int Max()
{   int sum=0;
CLR(Link,-1);
for(int i=1;i<=n;i++)
{   CLR(used,0);
if(Path(i)) sum++;
}
return sum;
}
void Graph()
{   for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(T[i].End+abs(T[i].a-T[j].x)+abs(T[i].b-T[j].y)<T[j].Start) map[i][j]=1;
}
int main()
{   int Case;
scanf("%d",&Case);
while(Case--)
{   scanf("%d",&n);
char time[10];
for(int i=1;i<=n;i++)
{   scanf("%s%d%d%d%d",time,&T[i].x,&T[i].y,&T[i].a,&T[i].b);
int t1,t2;
sscanf(time,"%d:%d",&t1,&t2);
T[i].Start=t1*60+t2;
T[i].End=T[i].Start+abs(T[i].x-T[i].a)+abs(T[i].y-T[i].b);
}
CLR(map,0);
Graph();
cout<<n-Max()<<endl;
}
return 0;
}

3、二分图的最大独立集=顶点数-最大匹配。(独立集:图中任意两个顶点都不相连的顶点集合)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: