【算法基础】染色法判定二分图、匈牙利算法

染色法判定二分图

算法描述:

二分图:有两顶点集且图中每条边的的两个顶点分别位于两个顶点集中,每个顶点集中没有边直接相连接!

二分图可能不是连通图,可能有很多连通分支。

对于染色法,我们需要用color数组来标记一个点的染色情况,由于是二分图,我们只分成两种情况,1 or 2。我们需要对整个图做遍历,相邻的点染色情况不一样,如果到最后出现了相邻点染色情况一样的情况,则说明这样一个图不是二分图。由于所给的图不一定是连通图,我们需要对未染色的点进行遍历,即dfs(未染色的结点)

#include<iostream>
#include<cstring>

using namespace std;
const int N = 1e5+10;
const int M = 2e5+10;
int n,m;
int h[N],e[M],ne[M],idx;
int color[N];

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;
}

void add(int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
int main()
{
    scanf("%d%d",&n,&m);
    
    memset(h,-1,sizeof(h));
    
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    
    bool flag=true;
    for(int i=1;i<=n;i++)
    {
        //由于在dfs的过程中会把那一块的连通块都染上色
        //所以需要先判断这个点是否已经染上了色,如果染了就不需要dfs了
        if(!color[i])
        {
            if(!dfs(i,1))
            {
                flag=false;
                break;
            }
        }
    }
    
    if(flag)puts("Yes");
    else puts("No");
    
    return 0;
}

经典习题:染色法判定二分图

匈牙利算法

算法描述:

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

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

我们把这个二分图分为两个集合,一个是n1,另一个是n2,我们对n1中的每个结点进行分配,直到不能分配为止。

为了保证分配的是最大匹配,我们会在遍历n1中的一个点时,会去考虑它所有的邻接点(由于时二分图,所以它的邻接点都是n2集合中的),如果这个邻接点没有n1集合中的点匹配,则由当前的这个点匹配;如果这个点已经被匹配了,则询问已匹配的这个n1集合的点能不能换一个点匹配

其实这里就相当于是在深度优先遍历了,只不过这个遍历当一个点遍历时会需求其他点以别的路径遍历以寻求最优解。

注意一下这里st数组的作用,如果可以不用要求别的n1集合中的点换匹配的点,其实st数组的作用不大,如果要求别的点更换匹配的点,就需要来看有哪些点在之前被考虑过,如果被考虑过可以直接跳过,因为考虑过的肯定是不可能实现的。

#include<iostream>
#include<cstring>

using namespace std;
const int N = 510,M = 100010;
int n1,n2,m;
int h[N],e[M],ne[M],idx;
int match[N];//n2集合匹配的n1中的点
bool st[N];//对于一个n1集合中的结点i,这个点是否考虑过n2的这个结点

void add(int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}

bool find(int x)//寻找x能不能找到匹配的
{
    for(int i=h[x];i!=-1;i=ne[i])
    {
        int j=e[i];//对应的邻接点,即n2集合中的
        if(!st[j])//如果这个点未被考虑过
        {
            st[j]=true;
            //如果这个点还未被匹配,或者已经匹配但是匹配的n1集合中的点
            // 可以再找别的去匹配
            if(!match[j]||find(match[j]))
            {
                match[j]=x;
                return true;
            }
        }
    }
    return false;
}

int main()
{
    scanf("%d%d%d",&n1,&n2,&m);
    
    memset(h,-1,sizeof(h));
    
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
    }
    
    int res=0;
    for(int i=1;i<=n1;i++)
    {
        memset(st,false,sizeof(st));
        if(find(i))res++;//如果i这个点可以找到匹配的,则++
    }
    
    cout<<res;
    
    return 0;
}

经典习题:二分图的最大匹配

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值