二分图匹配之匈牙利算法——杨子曰算法

二分图匹配之匈牙利算法——杨子曰算法

话说在一个相亲大会上,有n个男的和m个女的,每个男生都有自己喜欢的1个或几个女的,身为主办方的你要尽可能满足更多男生,问最多能匹配出多少对


今天我们来曰二分图,那二分图到底是什么鬼呢?

设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。

度娘说的话,我们是永远也不能理解的,我来告诉大家二分图到底是什么:就是一个图中,左边有一堆点,互不相连,右边有一堆点,也互不相连,但左右之间有边,Look at the 图:
这里写图片描述
这就是一张标准的二分图
然后我们来看什么是二分图匹配,像上面那个题,把左边的点看成男生,右边的点看成女生,问你最多能找出多少匹配,那怎么做呢?
大佬可以使用网络流(那是神马?),我们今天来讲一个更方便的算法——匈牙利算法,走起


匈牙利算法是通过对不停从每个点DFS寻找增广路实现的,从一个点出发,如果有能匹配的点,那就匹配上,如果发现所有连出去的边都已经被匹配了,就用占了这个点的那个点去找其他的匹配
肯定有人已经(✖_✖)了,那我们来模拟一下上面那个图:
首先1号节点连到5,发现5没有被占,OK连上
这里写图片描述
然后从2出发,发现连到了6,而且6也没被占,OK,连上
这里写图片描述
从3出发,连到5,Oh NOOOOOO!5被占了
这里写图片描述
那我们继续找增广路,我们用5找到1,看1的其他边,1还连到7,噢,7没有被占,完美,连上(如果这时7被占了,我们就要通过7找到连到7的那个点,通过这个点在找有没有它能连出去的边。显然,这时一个递归的过程)
这里写图片描述
然后是4,发现4连到6,嗯6被占了,那我们通过6找到2

这里写图片描述
发现2是真的连不出去了,没办法,就这样匹配完成
这里写图片描述
OK,这个找增广路DFS比较简单,走起:

//match[i]表示目前与i匹配的那个点
int find(int x){
    for (int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if (!vis[v]){
            vis[v]=1;
            if (match[v]==0 || find(match[v])){//如果v还没有被占,或占了v的那个点能连到其他地方
                match[v]=x;
                return 1;
            } 
        }
    }
    return 0;
}

总结一波,匈牙利算法通过从每个点出发找增广路,DFS复杂度为O(m),最终复杂度O(mn)
OK,完事


c++模板(HihoCoder-1122):

#include<bits/stdc++.h>
using namespace std;

int match[1005],head[1005],vis[1005],nedge=1;

struct Edge{
    int next,to;
}edge[10005];

void addedge(int a,int b){
    edge[nedge].to=b;
    edge[nedge].next=head[a];
    head[a]=nedge++;
}

int find(int x){
    for (int i=head[x];i!=-1;i=edge[i].next){
        int v=edge[i].to;
        if (!vis[v]){
            vis[v]=1;
            if (match[v]==0 || find(match[v])){
                match[v]=x;
                return 1;
            } 
        }
    }
    return 0;
}
int main(){
    memset(head,-1,sizeof(head));
    memset(match,0,sizeof(match));
    int n,m;
    scanf("%d%d",&n,&m);
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        addedge(a,b);
        addedge(b,a);
    }
    int ans=0;
    for (int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        if (find(i)) ans++;
    }
    cout<<ans/2;//因为我们把所有的点都扫了一遍,相当于左边一遍,右边一遍,所以每个匹配都算了两次,因此要除2
    return 0;
}

于 XJZX 507机房
未经作者允许,严禁转载:https://blog.csdn.net/HenryYang2018/article/details/81077821

猜你喜欢

转载自blog.csdn.net/HenryYang2018/article/details/81077821