二分图最大匹配总结

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

匈牙利算法的理解

转自 http://hi.baidu.com/blazar/item/7f120061e3b8572768105b3d

    设想一个公司里有A, B, C三种工具, 有员工1, 2, 3, 4号. 这四位员工分别能操作的机型为情况为: 

    1: A, B

    2: A, C

    3: A

    若想充分发挥所有人和所有机器的能力, 让生产力最大化, 我们不难安排1->B, 2->C, 3->A.

    当一个公司非常大, 机器种类非常多, 员工非常多, 那么此时就需要一种有效的算法来解决人员分配的问题. 而匈牙利算法就可以做到这一点.     

    上述问题中, 员工和机器可以看作一个二部图(或二分图, 见http://en.wikipedia.org/wiki/Bipartite_graph), 设G=(V,E)是一个无向图, 如果顶点V可分割为两个互不相交的子集(A,B), 并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A, j in B),则称图G为一个二分图. 上例中, 员工二分图的一个集合, 机器是另一个集合.     

    匈牙利算法进行时需要找一个叫做"交错路径"(或者交错树)的东西,  下边第一个图黑色实现标出了一个匹配,  而第二个图则给出了这个匹配的交错路径. 交错路径是针对匹配而言的. 

                                                                                         

 匈牙利算法的一个案例:

   1、起始没有匹配 

   

   2、选中第一个x点找第一跟连线 

     

   3、选中第二个点找第二跟连线 

    

   4、发现x3的第一条边x3y1已经被人占了,找出x3出发的的交错路径x3-y1-x1-y4,把交错路中已在匹配上的边x1y1从匹配中去掉,剩余的边x3y1 x1y4加到匹配中去 

    

   5、同理加入x4,x5。 

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

匈牙利算法模板(NOCOW)

求最大匹配的一种显而易见的算法是:先找出全部匹配,然后保留匹配数最多的。但是这个算法的复杂度为边数的指数级函数。因此,需要寻求一种更加高效的算法。

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

由增广路的定义可以推出下述三个结论:

  1. P的路径长度必定为奇数,第一条边和最后一条边都不属于M。
  2. P经过取反操作可以得到一个更大的匹配M’。
  3. M为G的最大匹配当且仅当不存在相对于M的增广路径。

用增广路求最大匹配(称作匈牙利算法,匈牙利数学家Edmonds于1965年提出)

算法轮廓:

  1. 置M为空
  2. 找出一条增广路径P,通过取反操作获得更大的匹配M’代替M
  3. 重复(2)操作直到找不出增广路径为止
#include<stdio.h>
#include<string.h>
 
bool g[201][201];
int n,m,ans;
bool b[201];
int link[201];
 
bool init()
{
        int _x,_y;
        memset(g,0,sizeof(g));
        memset(link,0,sizeof(link));
        ans=0;
        if(scanf("%d%d",&n,&m)==EOF)return false;
        for(int i=1;i<=n;i++)
        {
                scanf("%d",&_x);
                for(int j=0;j<_x;j++)
                {
                        scanf("%d",&_y);
                        g[ i ][_y]=true;
                }
        }
        return true;
}
 
bool find(int a)
{
        for(int i=1;i<=m;i++)
        {
                if(g[a][ i ]==1&&!b[ i ])
                {
                        b[ i ]=true;
                        if(link[ i ]==0||find(link[ i ]))
                        {
                                link[ i ]=a;
                                return true;
                        }
                }
        }
        return false;
}
 
int main()
{
        while(init())
        {
                for(int i=1;i<=n;i++)
                {
                        memset(b,0,sizeof(b));
                        if(find(i))ans++;
                }
                printf("%d\n",ans);
        }
}

每次增广时间为O(E),最多进行O(V)次迭代,时间复杂度为O(VE).

 

匈牙利算法扩展

转载于:http://www.cnblogs.com/kuangbin/archive/2012/08/26/2657446.html

1。一个二分图中的最大匹配数等于这个图中的最小点覆盖数

König定理是一个二分图中很重要的定理,它的意思是,一个二分图中的最大匹配数等于这个图中的最小点覆盖数。如果你还不知道什么是最小点覆盖,我也在这里说一下:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边。 

2。最小路径覆盖=|G|-最大匹配数

 在一个N*N的有向图中,路径覆盖就是在图中找一些路经,使之覆盖了图中的所有顶点, 且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点, 那么恰好可以经过图中的每个顶点一次且仅一次);如果不考虑图中存在回路,那么每每条路径就是一个弱连通子集.

由上面可以得出:

 1.一个单独的顶点是一条路径;
 2.如果存在一路径p1,p2,......pk,其中p1 为起点,pk为终点,那么在覆盖图中,顶点p1,p2,......pk不再与其它的
   顶点之间存在有向边.

最小路径覆盖就是找出最小的路径条数,使之成为G的一个路径覆盖. 

上图中,对应左边的DAG建立构造右边的二分图,可以找到二分图的一个最大匹配M:1-3',3-4',那么M中的这两条匹配边怎样对应DAG中的路径的边?使二分图中一条边对应DAG中的一条有向边,1-3'对应DAG图中的有向边1->3,这样DAG中1就会有一个后继顶点(3会是1的唯一后继,因为二分图中一个顶点至多关联一条边!),所以1不会成为DAG中一条路径中的结尾顶点,同样,3-4'对应DAG中3->4,3也不会成为结尾顶点,那么原图中总共4个顶点,减去2个有后继的顶点,就剩下没有后继的顶点,即DAG路径的结尾顶点,而每个结尾顶点正好对应DAG中的一条路径,二分图中寻找最大匹配M,就是找到了对应DAG中的非路径结尾顶点的最大数目,那么DAG中顶点数-|M|就是DAG中结尾顶点的最小数目,即DAG的最小路径覆盖数.

3。二分图最大独立集=顶点数-二分图最大匹配

独立集:图中任意两个顶点都不相连的顶点集合。

二分图模板: 

模板一:匈牙利算法

/* **************************************************************************
//二分图匹配(匈牙利算法的DFS实现)
//初始化:g[][]两边顶点的划分情况
//建立g[i][j]表示i->j的有向边就可以了,是左边向右边的匹配
//g没有边相连则初始化为0
//uN是匹配左边的顶点数,vN是匹配右边的顶点数
//调用:res=hungary();输出最大匹配数
//优点:适用于稠密图,DFS找增广路,实现简洁易于理解
//时间复杂度:O(VE)
//***************************************************************************/
//顶点编号从0开始的
const int MAXN=510;
int uN,vN;//u,v数目
int g[MAXN][MAXN];
int linker[MAXN];
bool used[MAXN];
bool dfs(int u)//从左边开始找增广路径
{
    int v;
    for(v=0;v<vN;v++)//这个顶点编号从0开始,若要从1开始需要修改
      if(g[u][v]&&!used[v])
      {
          used[v]=true;
          if(linker[v]==-1||dfs(linker[v]))
          {//找增广路,反向
              linker[v]=u;
              return true;
          }
      }
    return false;//这个不要忘了,经常忘记这句
}
int hungary()
{
    int res=0;
    int u;
    memset(linker,-1,sizeof(linker));
    for(u=0;u<uN;u++)
    {
        memset(used,0,sizeof(used));
        if(dfs(u)) res++;
    }
    return res;
}
//******************************************************************************/

 

模板二: Hopcroft-Carp算法

这个算法比匈牙利算法的时间复杂度要小,大数据可以采用这个算法

/* *********************************************
二分图匹配(Hopcroft-Carp的算法)。
初始化:g[][]邻接矩阵
调用:res=MaxMatch();  Nx,Ny要初始化!!!
时间复杂大为 O(V^0.5 E)
 
适用于数据较大的二分匹配
需要queue头文件
********************************************** */
const int MAXN=3000;
const int INF=1<<28;
int g[MAXN][MAXN],Mx[MAXN],My[MAXN],Nx,Ny;
int dx[MAXN],dy[MAXN],dis;
bool vst[MAXN];
bool searchP()
{
    queue<int>Q;
    dis=INF;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=0;i<Nx;i++)
        if(Mx[i]==-1)
        {
            Q.push(i);
            dx[i]=0;
        }
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        if(dx[u]>dis)  break;
        for(int v=0;v<Ny;v++)
            if(g[u][v]&&dy[v]==-1)
            {
                dy[v]=dx[u]+1;
                if(My[v]==-1)  dis=dy[v];
                else
                {
                    dx[My[v]]=dy[v]+1;
                    Q.push(My[v]);
                }
            }
    }
    return dis!=INF;
}
bool DFS(int u)
{
    for(int v=0;v<Ny;v++)
       if(!vst[v]&&g[u][v]&&dy[v]==dx[u]+1)
       {
           vst[v]=1;
           if(My[v]!=-1&&dy[v]==dis) continue;
           if(My[v]==-1||DFS(My[v]))
           {
               My[v]=u;
               Mx[u]=v;
               return 1;
           }
       }
    return 0;
}
int MaxMatch()
{
    int res=0;
    memset(Mx,-1,sizeof(Mx));
    memset(My,-1,sizeof(My));
    while(searchP())
    {
        memset(vst,0,sizeof(vst));
        for(int i=0;i<Nx;i++)
          if(Mx[i]==-1&&DFS(i))  res++;
    }
    return res;
}
//**************************************************************************/

 下面的程序效率很高。是用vector实现邻接表的匈牙利算法。

处理点比较多的效率很高。1500的点都没有问题

/*

HDU 1054

用STL中的vector建立邻接表实现匈牙利算法

效率比较高

 

 G++  578ms  580K

*/

#include<stdio.h>

#include<iostream>

#include<algorithm>

#include<string.h>

#include<vector>

using namespace std;

 

//************************************************

const int MAXN=1505;//这个值要超过两边个数的较大者,因为有linker

int linker[MAXN];

bool used[MAXN];

vector<int>map[MAXN];

int uN;

bool dfs(int u)

{

    for(int i=0;i<map[u].size();i++)

    {

        if(!used[map[u][i]])

        {

            used[map[u][i]]=true;

            if(linker[map[u][i]]==-1||dfs(linker[map[u][i]]))

            {

                linker[map[u][i]]=u;

                return true;

            }

        }

    }

    return false;

}

inthungary()

{

    int u;

    int res=0;

    memset(linker,-1,sizeof(linker));

    for(u=0;u<uN;u++)

    {

        memset(used,false,sizeof(used));

        if(dfs(u)) res++;

    }

    return res;

}

//*****************************************************

int main()

{

    int u,k,v;

    int n;

    while(scanf("%d",&n)!=EOF)

    {

        for(int i=0;i<MAXN;i++)

           map[i].clear();

        for(int i=0;i<n;i++)

        {

            scanf("%d:(%d)",&u,&k);

            while(k--)

            {

                scanf("%d",&v);

                map[u].push_back(v);

                map[v].push_back(u);

            }

        }

        uN=n;

        printf("%d\n",hungary()/2);

    }

    return 0;

}

例题:

POJ 3020  Antenna Placement

Time Limit: 1000MS

 

Memory Limit: 65536K

Total Submissions: 4739

 

Accepted: 2359

Description

The Global Aerial Research Centre has been allotted the task of building the fifth generation of mobile phone nets in Sweden. The most striking reason why they got the job, is their discovery of a new, highly noise resistant, antenna. It is called 4DAir, and comes in four types. Each type can only transmit and receive signals in a direction aligned with a (slightly skewed) latitudinal and longitudinal grid, because of the interacting electromagnetic field of the earth. The four types correspond to antennas operating in the directions north, west, south, and east, respectively. Below is an example picture of places of interest, depicted by twelve small rings, and nine 4DAir antennas depicted by ellipses covering them.

Obviously, it is desirable to use as few antennas as possible, but still provide coverage for each place of interest. We model the problem as follows: Let A be a rectangular matrix describing the surface of Sweden, where an entry of A either is a point of interest, which must be covered by at least one antenna, or empty space. Antennas can only be positioned at an entry in A. When an antenna is placed at row r and column c, this entry is considered covered, but also one of the neighbouring entries (c+1,r),(c,r+1),(c-1,r), or (c,r-1), is covered depending on the type chosen for this particular antenna. What is the least number of antennas for which there exists a placement in A such that all points of interest are covered?

Input

On the first row of input is a single positive integer n, specifying the number of scenarios that follow. Each scenario begins with a row containing two positive integers h and w, with 1 <= h <= 40 and 0 < w <= 10. Thereafter is a matrix presented, describing the points of interest in Sweden in the form of h lines, each containing w characters from the set ['*','o']. A '*'-character symbolises a point of interest, whereas a 'o'-character represents open space.

Output

For each scenario, output the minimum number of antennas necessary to cover all '*'-entries in the scenario's matrix, on a row of its own.

Sample Input

2

7 9

ooo**oooo

**oo*ooo*

o*oo**o**

ooooooooo

*******oo

o*o*oo*oo

*******oo

10 1

*

*

*

o

*

*

*

*

*

*

Sample Output

17

5

#include<stdio.h>

#include<algorithm>

#include<string.h>

#include<iostream>

using namespace std;

 

 

/* **************************************************************************

//二分图匹配(匈牙利算法的DFS实现)

//初始化:g[][]两边顶点的划分情况

//建立g[i][j]表示i->j的有向边就可以了,是左边向右边的匹配

//g没有边相连则初始化为0

//uN是匹配左边的顶点数,vN是匹配右边的顶点数

//调用:res=hungary();输出最大匹配数

//优点:适用于稠密图,DFS找增广路,实现简洁易于理解

//时间复杂度:O(VE)

//***************************************************************************/

//顶点编号从0开始的

const int MAXN=510;

int uN,vN;//u,v数目

int g[MAXN][MAXN];

int linker[MAXN];

bool used[MAXN];

bool dfs(int u)//从左边开始找增广路径

{

    int v;

    for(v=0;v<vN;v++)//这个顶点编号从0开始,若要从1开始需要修改

      if(g[u][v]&&!used[v])

      {

          used[v]=true;

          if(linker[v]==-1||dfs(linker[v]))

          {//找增广路,反向

              linker[v]=u;

              return true;

          }

      }

    return false;//这个不要忘了,经常忘记这句

}

int hungary()

{

    int res=0;

    int u;

    memset(linker,-1,sizeof(linker));

    for(u=0;u<uN;u++)

    {

        memset(used,0,sizeof(used));

        if(dfs(u)) res++;

    }

    return res;

}

//******************************************************************************/

 

char map[50][50];

int hash[50][50];

int main()

{

    int T;

    int h,w;

    scanf("%d",&T);

    int tol;

    while(T--)

    {

        scanf("%d%d",&h,&w);

        tol=0;

        for(int i=0;i<h;i++)

        {

            scanf("%s",&map[i]);

            for(int j=0;j<w;j++)

             if(map[i][j]=='*')

               hash[i][j]=tol++;

        }

        memset(g,0,sizeof(g));

        for(int i=0;i<h;i++)

          for(int j=0;j<w;j++)

            if(map[i][j]=='*')

            {

                if(i>0&&map[i-1][j]=='*')g[hash[i][j]][hash[i-1][j]]=1;

                if(i<h-1&&map[i+1][j]=='*') g[hash[i][j]][hash[i+1][j]]=1;

                if(j>0&&map[i][j-1]=='*')  g[hash[i][j]][hash[i][j-1]]=1;

                if(j<w-1&&map[i][j+1]=='*')  g[hash[i][j]][hash[i][j+1]]=1;

            }

        uN=vN=tol;

        printf("%d\n",tol-hungary()/2);

    }

    return 0;

}


 

Treasure Exploration

Time Limit: 6000MS

 

Memory Limit: 65536K

Total Submissions: 5480

 

Accepted: 2154

Description

Have you ever read any book about treasure exploration? Have you ever see any film about treasure exploration? Have you ever explored treasure? If you never have such experiences, you would never know what fun treasure exploring brings to you.
Recently, a company named EUC (Exploring the Unknown Company) plan to explore an unknown place on Mars, which is considered full of treasure. For fast development of technology and bad environment for human beings, EUC sends some robots to explore the treasure.
To make it easy, we use a graph, which is formed by N points (these N points are numbered from 1 to N), to represent the places to be explored. And some points are connected by one-way road, which means that, through the road, a robot can only move from one end to the other end, but cannot move back. For some unknown reasons, there is no circle in this graph. The robots can be sent to any point from Earth by rockets. After landing, the robot can visit some points through the roads, and it can choose some points, which are on its roads, to explore. You should notice that the roads of two different robots may contain some same point.
For financial reason, EUC wants to use minimal number of robots to explore all the points on Mars.
As an ICPCer, who has excellent programming skill, can your help EUC?

Input

The input will consist of several test cases. For each test case, two integers N (1 <= N <= 500) and M (0 <= M <= 5000) are given in the first line, indicating the number of points and the number of one-way roads in the graph respectively. Each of the following M lines contains two different integers A and B, indicating there is a one-way from A to B (0 < A, B <= N). The input is terminated by a single line with two zeros.

Output

For each test of the input, print a line containing the least robots needed.

Sample Input

1 0

2 1

1 2

2 0

0 0

Sample Output

1

1

2

此题跟普通的路径覆盖有点不同。

You should notice that the roads of two different robots may contain some same point.

也就是不同的路径可以有重复点。

先用floyed求闭包,就是把间接相连的点也连起来。

/*

POJ 2594

求最大独立集=顶点数-最大匹配数

*/

#include<stdio.h>

#include<string.h>

#include<algorithm>

#include<iostream>

using namespace std;

/* **************************************************************************

//二分图匹配(匈牙利算法的DFS实现)

//初始化:g[][]两边顶点的划分情况

//建立g[i][j]表示i->j的有向边就可以了,是左边向右边的匹配

//g没有边相连则初始化为0

//uN是匹配左边的顶点数,vN是匹配右边的顶点数

//调用:res=hungary();输出最大匹配数

//优点:适用于稠密图,DFS找增广路,实现简洁易于理解

//时间复杂度:O(VE)

//***************************************************************************/

//顶点编号从0开始的

const int MAXN=510;

int uN,vN;//u,v数目

int g[MAXN][MAXN];

int linker[MAXN];

bool used[MAXN];

bool dfs(int u)//从左边开始找增广路径

{

    int v;

    for(v=0;v<vN;v++)//这个顶点编号从0开始,若要从1开始需要修改

      if(g[u][v]&&!used[v])

      {

          used[v]=true;

          if(linker[v]==-1||dfs(linker[v]))

          {//找增广路,反向

              linker[v]=u;

              return true;

          }

      }

    return false;//这个不要忘了,经常忘记这句

}

int hungary()

{

    int res=0;

    int u;

    memset(linker,-1,sizeof(linker));

    for(u=0;u<uN;u++)

    {

        memset(used,0,sizeof(used));

        if(dfs(u)) res++;

    }

    return res;

}

//******************************************************************************/

 

void floyed(int n)//求传递闭包

{

    for(int i=0;i<n;i++)

      for(int j=0;j<n;j++)

      {

          if(g[i][j]==0)

          {

              for(int k=0;k<n;k++)

              {

                  if(g[i][k]==1&&g[k][j]==1)

                  {

                      g[i][j]=1;

                      break;

                  }

              }

          }

      }

}

 

int main()

{

    int n,m;

    int u,v;

    while(scanf("%d%d",&n,&m))

    {

        if(n==0&&m==0)break;

        uN=vN=n;

        memset(g,0,sizeof(g));

        while(m--)

        {

            scanf("%d%d",&u,&v);

            u--;v--;

            g[u][v]=1;

        }

        floyed(n);

        printf("%d\n",n-hungary());

    }

    return 0;

}


 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值