二分图的最大匹配

来源:互联网 发布:白鲨外设淘宝店 编辑:程序博客网 时间:2024/05/19 04:07

二分图:图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所示,(15)和(26)在图1中是两对已经配好对的点;而起点3和终点4目前还没有与其它点配对.)
(6)路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。(如图1、图2所示,原有的匹配是(15)和(26),这两条配匹的边在图2给出的增广路径中分边是第2和第4条边。而增广路径的第135条边都没有出现在图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[N][N],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、二分图的最大独立集=顶点数-最大匹配。(独立集:图中任意两个顶点都不相连的顶点集合)
 

 

原创粉丝点击