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

版权声明:转载请注明 https://blog.csdn.net/li13168690086/article/details/81515471

  写在之前:更多二分图知识,请关注--->二分图知识导航篇

定义

  摘自百度百科:匈牙利算法(Hungarian method)是由匈牙利数学家Edmonds于1965年提出,因而得名。匈牙利算法是基于Hall定理中充分性证明的思想,它是二分图匹配最常见的算法,该算法的核心就是寻找增广径,它是一种用增广路径求二分图最大匹配的算法。

  根据介绍,匈牙利算法就是一个不断寻找增广路径的过程。既然这样,我们就得先讲讲在图里,什么叫增广路径了。

增广路

  顾名思义,增加的更宽广的路径,也就是增加一条走的更远的路径。

  假设我们一开始在图里找到了第一条路,设集合A里的点A连接了集合Y点B。然后再找集合A里下一个顶点C,发现他想要连接的顶点是点B,但被刚才点A占了,那就出现了矛盾。于是我们退而求其次,问问点A还有没别的点可以连,如果有点D,就好办了,请点A连点D吧,那点C就可以连点B了,路径就是:C-->B-->A-->D,形成了3条边C-B, B-A, A-D。

  •   一共有3条边,奇数,性质1
  •   起点是点C,和点A是同一集合X,终点是点D属于Y集合,且点C和点D是新点,即没匹配的点,性质2、5
  •   路径简化后为C-B-A-D,可见整条路上没有重复的点,且点是交替在X和Y集出现的,性质3,4
  •   第1边C-B,第3边A-D都是新出现的边,第2条边是已经记录过的边,性质6
  •   当我们把B-A删掉后,图上剩下的边就是C-B, A-D,这就是新形成的匹配子图,匹配数为2,性质7
    图1
    图1

        但是如果点A没有其他可连,那就只能看点C可不可以连其他未匹配的点,如果有点E,那就将点C连点E,然后继续寻找下一个匹配点;若X集内顶点全部匹配完成,或没有任何可匹配的点,也就是说新的增广路也找不到了,那就认为这个图最大匹配已经形成。

增广路性质

1. 有奇数条边 。
2. 起点在二分图的X边,终点在二分图的Y边 。
3. 路径上的点一定是一个在X边,一个在Y边,交错出现。 
4. 整条路径上没有重复的点 。
5. 起点和终点都是目前还没有配对的点,其他的点都已经出现在匹配子图中 。
6. 路径上的所有第奇数条边都是目前还没有进入目前的匹配子图的边,而所有第偶数条边都已经进入目前的匹配子图,奇数边比偶数边多一条边 。
7. 于是当我们把所有第奇数条边都加到匹配子图并把条偶数条边都删除,匹配数增加了1。

最大匹配

  最大匹配,取最大。意思是这个图里,能够达到的点对点匹配数最大的状态。就像上面说的,当再也找不到新的增广路或者已经匹配完所有可匹配的点后,就是匹配数最大的状态了。而有个词叫完美匹配,也就是图上任意一个点都成功匹配,那么就完美了,也就完全了,所以完美匹配一定是最大匹配。

基本匈牙利算法实现原理

  •   首先我们需要一个m,n用来记录x集和y集内顶点数,和一个最大顶点数MAX_N,还有一个最大匹配数sum;
  •   然后一个二维数组line[MAX_N][MAX_N],来记录最初哪些点存在关系,即以后可以选择连线的点;和一个变量k,用于记录有多少对这样的关系;
  •   再有一个数组boy[MAX_N]用来记录,Y集内的顶点被X集哪些顶点选择了,即Y集内顶点的状态,被谁选择;
  •   最后一个数组used[MAX_N]很有意思,它是在每轮X集内顶点进行选择时,用来记录Y集内顶点的状态,和boy不一样的是,boy主要是用来记录Y集顶点被谁选择,而used是表示Y集顶点是否可选。因为在对新的X集顶点进行选择时,之前已经搭配好的Y集将被视为可选状态,是随时可被拆散的。所以used数组在对下一个X集顶点进行匹配前,要全部初始化为0,而boy则不用。
  • 原理内核
        int x,y,sum; 
        scanf("%d %d %d",&k,&m,&n);   
        memset(line, 0, sizeof(line));
        memset(boy, 0, sizeof(boy));
        for(int i = 0; i < k; i++){  
            scanf("%d %d",&x,&y);
            line[x][y] = 1;    
        }
        sum = 0;
        for(int i = 1; i <= m; i++){
            memset(used, 0, sizeof(used));     
            if(found(i))   
              sum++;  
        }
        printf("%d\n",sum);
    bool found(int x){
        for(int i = 1; i <= n; i++){  
            if(line[x][i] && !used[i]){  
                used[i] = 1;   
                if(boy[i] == 0 || found(boy[i])){  
                    boy[i] = x;     
                    return 1;      
                }
            }
        }
        return 0;  
    }

    补充说明:在这里X集是出发集,Y集是被匹配集,出发集被匹配集的意思是:从集合A出发在集合B里找匹配点

下面请看一道例题

描述

RPG girls今天和大家一起去游乐场玩,终于可以坐上梦寐以求的过山车了。可是,过山车的每一排只有两个座位,而且还有条不成文的规矩,就是每个女生必须找个个男生做partner和她同坐。但是,每个女孩都有各自的想法,举个例子把,Rabbit只愿意和XHD或PQK做partner,Grass只愿意和linle或LL做partner,PrincessSnow愿意和水域浪子或伪酷儿做partner。考虑到经费问题,boss刘决定只让找到partner的人去坐过山车,其他的人,嘿嘿,就站在下面看着吧。聪明的Acmer,你可以帮忙算算最多有多少对组合可以坐上过山车吗?

Input

输入数据的第一行是三个整数K , M , N,分别表示可能的组合数目,女生的人数,男生的人数。0<K<=1000 
1<=N 和M<=500.接下来的K行,每行有两个数,分别表示女生Ai愿意和男生Bj做partner。最后一个0结束输入。

Output

对于每组数据,输出一个整数,表示可以坐上过山车的最多组合数。

Sample Input

6 3 3
1 1
1 2
1 3
2 1
2 3
3 1
0

Sample Output

3

解题思路

  这题就是很明显,跟二分图最大匹配概念一样的意思,于是我将在AC代码里更加细致的讲解匈牙利算法。

AC代码

#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
#include <math.h>
using namespace std;
const int N = 505;
int line[N][N];     //新建可搭配数组
int boy[N],used[N];    //新建男生配对数组,和搭配标记数组
int k,m,n;

bool found(int x){
    for(int i = 1; i <= n; i++){    //遍历所有男生
        if(line[x][i] && !used[i]){     //如果第x个女生和第i个男生有关系,并且男生还没被选,就进行选择
            used[i] = 1;    //第i个男生被标记已选,为了防止递归时,再次被选
            if(boy[i] == 0 || found(boy[i])){   //如果此男生没有被任何人选择过,或者说选择男生的那位女生还有其他可选的对象(DFS)
                boy[i] = x;     //将当前男生选给当前女生
                return 1;       //并返回1
            }
        }
    }
    return 0;   //如果没有选择的对象,就返回0
}

int main(){
    int x,y;
    while(scanf("%d",&k) && k){     //读入k,若k为0则结束循环
        scanf("%d %d",&m,&n);   //读入m,n,女生和男生的数量
        memset(line, 0, sizeof(line));
        memset(boy, 0, sizeof(boy));
        for(int i = 0; i < k; i++){    //读入可搭配的女生和男生
            scanf("%d %d",&x,&y);
            line[x][y] = 1;     //两者连线,标记为1
        }
        int sum = 0;    //最后可配对数量
        for(int i = 1; i <= m; i++){
            memset(used, 0, sizeof(used));      //每次配对时,将所有男生看作都还没确定搭配对象
            if(found(i))    //配对第i位女生
                sum++;  //如果成功,总数加1
        }
        printf("%d\n",sum);
    }
    return 0;
}

  此外最大匹配与最小覆盖集有很大的相交点,所以请关注二分图知识导航篇

猜你喜欢

转载自blog.csdn.net/li13168690086/article/details/81515471
今日推荐