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

目录

一、什么是二分图?

二、染色法

三、匈牙利算法

总结


一、什么是二分图?

        二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。

        即所有点被划分为两个集合,所有的边连接存在不同集合中的点;每个集合中不存在边

二分图中当且仅当不存在奇数环 

二、染色法

        染色法用于判断一个图是不是二分图;由于二分图中不存在奇数环,所以染色过程不存在矛盾;

        对于每一个顶点若确定了颜色,那么便可确定整张图的所有顶点的颜色;且每条边的两个顶点的颜色一定不同。

        核心思想:dfs判断以当前节点为根的子树中是否存在矛盾

模板题:

链接:https://www.acwing.com/problem/content/862/

/**
 * 染色法:判断一个图是不是二分图
 *  dfs的方式每次染色以当前节点为根的子树,只要发生矛盾,则不是二分图
 */ 
 
 #include <iostream>
 #include <cstdio>
 #include <cstring>
 
 using namespace std;
 const int N = 100010;
 int n, m;
 int e[N * 2], ne[N * 2], h[N], idx;
 int match[N]; // 记录染色的数字
 
 void add(int a, int b) {
     e[idx] = b;
     ne[idx] = h[a];
     h[a] = idx ++;
 }
 
 bool dfs(int x, int color) {
     match[x] = color;
     
     for (int i = h[x]; i != -1; i = ne[i]) {
         int j = e[i];
         if (!match[j]) {
             if(!dfs(j, 3 - color)) return false;
         } else if (match[j] == color) return false;
     }
     
     return true;
 }
 
 int main()
 {
     memset(h, -1, sizeof h);
     cin >> n >> m;
     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 (!match[i]) {
             // dfs含义:对以i为根的字数进行染色,其中顶点i的颜色为1
             if (!dfs(i, 1)) {
                 flag = false;
                 break;
             } 
         }
     }

     if (flag) puts("Yes");
     else puts("No");
     return 0;
 }

三、匈牙利算法

        判断二分图最大匹配。时间复杂度最坏O(n * m);但实际远小于这个理论

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

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

        算法思路:将二分图中的两个集合看做男生和女生,进行匹配。假设只遍历男生集合,选择第一个女生与其匹配;如果选择的女生已经被选,那么试着改变女生匹配的男生换一个人选;否则只能放弃

        因此,在构建图的时候只需要保证一个方向的边就行,即有向图

模板题:

链接:https://www.acwing.com/problem/content/863/

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
const int N = 510, M = 100010;
int e[M], ne[M], h[N], idx;
int match[N]; // 存储匹配的男生
bool vis[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 (!vis[j]) {
            vis[j] = true;
            if (match[j] == 0 || find(match[j])) {
                match[j] = x;
                return true;
            }
        } 
    }
    return false;
}


int main()
{
    memset(h, -1, sizeof h);
    int n1, n2, m;
    scanf("%d%d%d", &n1, &n2, &m);
    while (m --) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
    }
    // 遍历男生集合
    int res = 0;
    for (int i = 1; i <= n1; i++) {
        memset(vis, false, sizeof vis);
        if(find(i)) { // 寻找i匹配的点
            res ++;
        }
    }
    printf("%d\n", res);
    return 0;
}

总结

        刚接触二分图,记录下学习到的两个算法

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值