[算法系列]图论04-二分图基础

171 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

二分图基础

二分图,又叫二部图,简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻的图。

染色法判定二分图

基本性质: 一个图是二分图等价于该图中不含有奇数环,这是一个充要条件

我们通过将节点染成不同的颜色,以此标记它们所属的集合,不含奇数环,染色就没有矛盾

判定原理: 一个图在染色过程中,出现矛盾,则不是二分图,否则就是二分图

染色过程我们可以借助DFS或者BFS实现

代码实现:

例题:给你n个顶点m个边的图,输入为m行,每行两个数表示边,输出为"Yes"或者“No”表示是不是二分图

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
​
const int N=1e5+10,M=2e5+20;
int h[N],e[M],ne[M],idx;
int color[N];
bool st[N];
int n,m;
void add(int a,int b){
    e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
bool dfs(int x,int c){
    color[x]=c;                           
    for(int i=h[x];i!=-1;i=ne[i]){     
        int j=e[i];                 
        if(!color[j]){
            if(!dfs(j,3-c)){
                return false;
            }
        }
        else{
            if(color[j]==c){
                return false;
            }
        }
    }
    return true;
}
int main(){
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);
        add(b,a);
    }
    bool flag=true;           //flag记录是否出现矛盾
    for(int i=1;i<=n;i++){    //for循环,找没染色的点
        if(!color[i]){
            if(!dfs(i,1)){
                flag=false;
                break;
            }
        }
    }
    if(flag) puts("Yes");
    else puts("No");
    return 0;
}

匈牙利算法

匈牙利算法常被用于求解二分图的最大匹配问题

二分图的匹配: 给定一个二分图 G,在 GG的一个子图 M 中,M的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。

二分图的最大匹配: 所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

算法步骤:

我们不妨把二分图的两个部分看作是男和女

1.遍历每个男生,每次遍历将vis数组清空,vis数组是为了保证对于每个男生而言,每个女生只考虑一次

2.每次考虑的时候,遍历该男生"看中"(相接)的所有女生,如果对应女生没有与其他男生匹配或是匹配的男生有“备胎”,那么这对男女就匹配,统计匹配成功的数量,就是二分图的最大匹配

由上述分析可知,该算法的时间复杂度是O(mn)

代码实现:

例题:

给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m条边。

数据保证任意一条边的两个端点都不可能在同一部分中。

请你求出二分图的最大匹配数。

输入:

第一行包含三个整数 n1、 n2 和 m。

接下来 m 行,每行包含两个整数 u 和 v,表示左半部点集中的点 u 和右半部点集中的点 v之间存在一条边。

输出:

输出一个整数,表示二分图的最大匹配数。

AC Code:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e5+10,M=4e5+10;
int n1,n2,m;
int e[M],ne[M],h[N],idx;
void add(int a,int b){
    e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
int match[N];
bool st[N];
bool find(int x){
    for(int i=h[x];~i;i=ne[i]){
        int j=e[i];
        if(!st[j]){
            st[j]=true;
            if(match[j]==0||find(match[j])){
                match[j]=x;
                return true;
            }
        }
    }
    return false;
}
​
int main(){
    cin>>n1>>n2>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    int res=0;
    for(int i=1;i<=n1;i++){
        memset(st,0,sizeof st);
        if(find(i))res++;
    }
    cout<<res<<endl;
    return 0;
}