二分图的最大匹配 (匈牙利算法)

1、二分图、最大匹配

什么是二分图:二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
4f7c595e-020c-3f6e-b20f-55630df1781a.jpg
什么是匹配:把上图想象成3男4女搞对象(无同性恋),连线代表彼此有好感,但最终只能1夫1妻,最终的配对结果连线就是一个匹配。匹配可以是空。
什么是最大匹配:在有好感的基础上,能够最多发展几对。

现在要用匈牙利算法找出最多能发展几对。
[color=green][size=medium]
匈牙利算法是解决寻找二分图最大匹配的。


二、最大匹配与最小点覆盖

最小点覆盖:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边

最小割定理是一个二分图中很重要的定理:一个二分图中的最大匹配数等于这个图中的最小点覆盖数。


最小点集覆盖==最大匹配。在这里解释一下原因,首先,最小点集覆盖一定>=最大匹配,因为假设最大匹配为n,那么我们就得到了n条互不相邻的边,光覆盖这些边就要用到n个点。现在我们来思考为什么最小点击覆盖一定<=最大匹配。任何一种n个点的最小点击覆盖,一定可以转化成一个n的最大匹配。因为最小点集覆盖中的每个点都能找到至少一条只有一个端点在点集中的边(如果找不到则说明该点所有的边的另外一个端点都被覆盖,所以该点则没必要被覆盖,和它在最小点集覆盖中相矛盾),只要每个端点都选择一个这样的边,就必然能转化为一个匹配数与点集覆盖的点数相等的匹配方案。所以最大匹配至少为最小点集覆盖数,即最小点击覆盖一定<=最大匹配。综上,二者相等。


三、匈牙利算法

先给一个例子
1、起始没有匹配
d9b44964-f903-3b3d-85fb-22994a3009f9.jpg
2、选中第一个x点找第一跟连线
3aa69867-6d31-3166-bc70-675ee19dfc1c.jpg
3、选中第二个点找第二跟连线
ce8d2b7d-eff7-3232-8a49-5d4de5e75b0d.jpg
4、发现x3的第一条边x3y1已经被人占了,找出x3出发的的交错路径x3-y1-x1-y4,把交错路中已在匹配上的边x1y1从匹配中去掉,剩余的边x3y1 x1y4加到匹配中去
3e61fee1-03fa-3daf-93ee-03cb9b4aca1d.jpg
5、同理加入x4,x5。

匈牙利算法可以深度有限或者广度优先,刚才的示例是深度优先,即x3找y1,y1已经有匹配,则找交错路。若是广度优先,应为:x3找y1,y1有匹配,x3找y2。


深度优先匈牙利算法代码


#define maxn 10//表示x集合和y集合中顶点的最大个数!
 int nx,ny;//x集合和y集合中顶点的个数
 int edge[maxn][maxn];//edge[i][j]为1表示ij可以匹配
 int cx[maxn],cy[maxn];//用来记录x集合中匹配的y元素是哪个!
 int visited[maxn];//用来记录该顶点是否被访问过!
 int path(int u)
 {
     int v;
     for(v=0;v<ny;v++)
     {
         if(edge[u][v]&&!visited[v])
         {
             visited[v]=1;
            if(cy[v]==-1||path(cy[v]))//如果y集合中的v元素没有匹配或者是v已经匹配,但是从cy[v]中能够找到一条增广路
             {
                 cx[u]=v;
                 cy[v]=u;
                 return 1;
             }
         }
     }
     return 0;
 }
 int maxmatch()
 {
     int res=0;
     memset(cx,0xff,sizeof(cx));//初始值为-1表示两个集合中都没有匹配的元素!
     memset(cy,0xff,sizeof(cy));
     for(int i=0;i<=nx;i++)
     {
         if(cx[i]==-1)
         {
             memset(visited,0,sizeof(visitited));
             res+=path(i);
         }
     }
     return res;
 }


四、相关POJ题目

(1) poj3041

题目意思就是一颗子弹可以干掉任意一行或一列的障碍,问最少需要花费多少子弹清除呢

也就是求最小点集覆盖。


#include<iostream>
#include<stdio.h>
#include<string.h>
#define Max 505
using namespace std;
int a[Max][Max];
int visit[Max];
int match[Max];
int N,K;
int path(int u)
{
    int v;
    for(v=1;v<=N;v++)
    {
          if(a[u][v] && !visit[v])
          {
                visit[v] = 1;
                if(match[v] == -1 || path(match[v]))
                {
                            match[v] = u;
                            return 1;
                }   
          }
    }
    return 0;
}
int main()
{
                                                                                                         
      int i,j,k,count;
      scanf("%d %d",&N,&K);
                                                                                                         
      memset(a,0,sizeof(a));
      memset(match,-1,sizeof(match));
      count = 0;
      for(i=1;i<=K;i++)
      {
           scanf("%d %d",&j,&k);
           a[j][k] = 1;
      }
                                                                                                           
      for(i=1;i<=N;i++)
      {
             memset(visit,0,sizeof(visit));
             if(path(i))
               count++;
      }
      printf("%d\n",count);
                                                                                                         
      return 0;
}


(2) poj3020

此题重要的是做出图来,以什么样的观点作图。可以画 h*w -> h*w 的图,点i与上下左右四个点有边存在,求最大匹配,最后结果为 总点数 - 最大匹配/2


#include<iostream>
#include<stdio.h>
#include<string.h>
#define Max 520
using namespace std;
int a[Max][Max];
int visit[Max];
int match[Max];
int N;
char str[50][15];
int path(int u)
{
    int v;
    for(v=1;v<=N;v++)
    {
          if(a[u][v] && !visit[v])
          {
                visit[v] = 1;
                if(match[v] == -1 || path(match[v]))
                {
                            match[v] = u;
                            return 1;
                }   
          }
    }
    return 0;
}
int Find()
{
    int count = 0;
    int i;
    for(i=1;i<=N;i++)
    {
             memset(visit,0,sizeof(visit));
             if(path(i))
               count++;
    }
    return count;
}
int init(int h,int w) {
    int ctr,i,j;
    int x,y;
    int s,d;
    ctr=0;
    for ( i=1;i<=h;i++ ) {
        for ( j=1;j<=w;j++ ) {
            if ( str[i][j]=='*' ) {
                ctr++;
                x=i;
                y=j;
                s=(x-1)*w+y;
                if ( y+1<=w&&str[x][y+1]=='*' ) {
                    d=(x-1)*w+y+1;
                    a[s][d]=1;
                }
                if ( x+1<=h&&str[x+1][y]=='*' ) {
                    d=(x)*w+y;
                    a[s][d]=1;
                }
                if ( y-1>=1&&str[x][y-1]=='*' ) {
                    d=(x-1)*w+y-1;
                    a[s][d]=1;
                }
                if ( x-1>=1&&str[x-1][y]=='*' ) {
                    d=(x-2)*w+y;
                    a[s][d]=1;
                }
            }
        }
    }
    return ctr;
}
int main()
{
                           
      int i,j,k,n,totalnum,matchnum,h,w;
      scanf("%d",&n);
      while(n--)
      {  
          memset(a,0,sizeof(a));
          memset(match,-1,sizeof(match));
                                 
          scanf("%d %d",&h,&w);
          getchar();
          for(i=1;i<=h;i++)
          {
              for(j=1;j<=w;j++)
              {
                 scanf("%c",&str[i][j]);
              }
              getchar();
          }
                                 
          totalnum = init(h,w);
          N = h*w;
          matchnum = Find();
                                 
          printf("%d\n",totalnum - matchnum/2);
                                                
                                 
      }
      return 0;
}二分图






你可能感兴趣的:(二分图,最大匹配,匈牙利算法,最小集覆盖)