二分图(染色法、匈牙利算法)


主要内容:
在这里插入图片描述

染色法判定二分图

  • 二分图:把无向图分为两个集合V1, V2,所有边都在V1和V2之间,V1或V2内部没有边。一个图是否为二分图,一般用"染色法"判断。
    例如:
    在这里插入图片描述
    (1、2在一个集合,3,4在一个集合)

  • 染色法:用两种颜色(可以赋值为1、2)对所有顶点进行染色,要求一条边所连的两个相邻顶点的颜色不同,颜色结束后,若所有相邻顶点的颜色都不相同,就是二分图。

二分图一定不是奇数环(如下图,奇数环存在相邻两个点颜色相同的情况,说明它们在一个集合内)
在这里插入图片描述

我们可以用DFS来实现:

代码思路:

  1. 用1、2表示不同颜色,0表示未染色
  2. dfs遍历所有点。将未染色的点进行染色
  3. 如果当前点未染色,染成另一种颜色,染色失败返回false,如果当前点染过色但与邻点撞色,说明两点在同一集合.
bool dfs(int u, int c)
{
    color[u] = c;
    for(int i = h[u]; 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;//未发生矛盾
}

在这里插入图片描述

#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010, M = 200010;//因为是双向边所以m扩大
int n, m;
int h[N], e[M], ne[M], idx;
int color[N];
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

bool dfs(int u, int c)
{
    color[u] = c;
    for(int i = h[u]; 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;//判断是否有矛盾发生

    for(int i = 1; i <= n; i ++ )
        if(!color[i])//未被染色
        {
            if(!dfs(i, 1))//如果染1有矛盾发生
            {
                flag = false;//不是二分图
                break;
            }
        }

    if(flag) puts("Yes");
    else puts("No");

    return 0;
}

复杂度O(n + m)


匈牙利算法

使用匈牙利算法求二分图的最大匹配数

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

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

算法分析:

在这里插入图片描述
图中点3条红线表示匹配成功,匹配的过程分析:

类似于男女匹配,男的先匹配第一次遇见的姑娘,然后往下看另一个男生,如果两人喜欢同一个姑娘,那这个姑娘如何处理?1号姑娘观察1号男生还喜欢2号女生,为了形成一个皆大欢喜的局面,男1女2匹配,男2女1匹配。3号男生虽然也喜欢2号女生,不过人家有对象了,还好他没吊死在一棵树上,匹配3号女生。

在这里插入图片描述

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;
bool st[N];
int h[N], e[M], ne[M], idx;
int match[N];//姑娘对应的男孩
void add(int a, int b)
{//稀疏图用邻接表
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool find(int x)
{
    for(int i = h[x]; i != -1; i = ne[i] )//遍历男生看上的姑娘
    {
        int j = e[i];

        if(!st[j])
        {
            st[j] = true;

            if(match[j] == 0 || find(match[j]))//姑娘未匹配, 或者与姑娘匹配的男生可以选择另一个妹子
            {
                match[j] = x;//姑娘与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, false, sizeof st);//清空所有姑娘

        if(find(i)) res ++ ;
    }

    cout << res << endl;

    return 0;
}

复杂度分析:

考虑每个男生(共n个),最坏情况下遍历所有边(m条),最坏情况下是O(nm),实际情况是并不需要遍历完所有边,所以复杂度远小于O(nm).

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章