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

news2024/11/15 8:25:21

目录

  • 二分图算法总览
  • 二分图的概念
    • 1.二分图的定义
    • 2.二分图的特点
    • 3.二分图的应用
  • 染色法(判断二分图)
    • 算法步骤
    • 算法运用
      • 染色法判定二分图
  • 匈牙利算法(计算二分图的最大匹配)
    • 二分图的匹配
    • 算法步骤
    • 算法应用
      • 二分图的最大匹配


二分图算法总览

二分图的概念


二分图的概念

1.二分图的定义

一个图 G = ( V , E ) G=(V,E) G=(V,E) 被称为二分图(Bipartite Graph),当且仅当顶点集 V V V 可以分割成两个互不相交的子集 U U U W W W,使得 E E E 中的每一条边都连接一个 U U U 中的顶点和一个 W W W 中的顶点。

换句话说,二分图就是图中的顶点可以分成两类,每条边都只连接这两类顶点中的一个。


例如:
在这里插入图片描述
在这里插入图片描述
这就是一个明显的二分图,集合A与B中的点互不相连。


2.二分图的特点

  1. G G G 中的顶点可以分成两个互不相交的子集 U U U W W W V = U ∪ W V=U∪W V=UW
  2. 对于任意一条边 ( u , w ) (u,w) (u,w),必有 u ∈ U u∈U uU w ∈ W w∈W wW。也就是说,每条边都连接 U U U W W W 中的顶点。
  3. 不存在属于同一顶点集的两点之间有边相连。 U U U 中的顶点只连接 W W W 中的顶点, W W W 中的顶点只连接 U U U 中的顶点。
  4. 一个图是二分图,当且仅当它不包含奇数长度的环。

注意

  • 二分图当且仅当图中没有奇数环
  • 当图中没有奇数环一定是二分图
  • 任何无回路的图均是二分图
    在这里插入图片描述
  • 二分图不一定是连通图

二分图不一定是连通图


3.二分图的应用

二分图的一些常见应用场景包括:

  1. 匹配问题
    二分图可以用于描述匹配关系。例如,在求解Stable Marriage问题时,男生集合和女生集合构成二分图的两个顶点集,边表示男生和女生之间的偏好。求解这个二分图的最大匹配,就可以得到一个稳定的匹配方案。

  2. 网络流问题
    很多网络流问题可以建模为二分图。例如在网络最大流问题中,源点集和汇点集分别作为二分图的两个顶点集,边和边上的流量构成二分图。求解这个二分图的最大流就等价于求原网络的最大流。

  3. 图的着色问题
    如果将每个颜色看成一个顶点集,图的节点看成另一个顶点集,则图的着色问题可以转换为在对应的二分图中求最大匹配。

  4. 关系建模
    二分图可以建模表达两个不同类型实体集合之间的关系。例如,学生-课程的关系可以用学生集合和课程集合构成的二分图来表示。

  5. 调度问题
    将任务看成一个顶点集,处理器资源看成另一个顶点集,二分图的边表示任务和处理器之间的关系,求解二分图的最大匹配可以用于调度资源。


染色法(判断二分图)

染色法是判断图是否为二分图的一种算法。

基本思想是:

①为图 G G G 中的每个顶点赋予红色或蓝色这两种不同的颜色。

②如果存在一条边的两个端点颜色相同,则该图不是二分图。

③如果所有的边两端点颜色均不同,则该图是二分图。

算法步骤

  1. 创建一个标记数组 c o l o r [ ] color[ ] color[],将所有顶点标记为未染色,初始化为 0 0 0

  2. 遍历所有顶点,对于每个未染色的顶点 v v v
    (1) 使用红色或蓝色对其染色, c o l o r [ v ] = 1 color[v] = 1 color[v]=1 2 2 2
    (2) 将与顶点 v v v 相连的所有顶点染成不同颜色。

  3. 检查每条边的两个端点颜色是否不同:
    (1) 若存在一条边其两个端点颜色相同,则返回 false,该图不是二分图。
    (2) 若所有边两端点颜色均不同,则返回 true,该图是二分图。

分析:

  • 时间复杂度 O ( n + m ) O(n + m) O(n+m),需要遍历所有顶点和边。
  • 空间复杂度 O ( n ) O(n) O(n),需要颜色标记数组。
  • 可以在线性时间内判断二分图,效率较高。

算法运用

染色法判定二分图

题目描述:
给定一个 n n n 个点 m m m 条边的无向图,图中可能存在重边和自环。

请你判断这个图是否是二分图。

输入格式:
第一行包含两个整数 n n n m m m

接下来 m m m 行,每行包含两个整数 u u u v v v,表示点 u u u 和点 v v v 之间存在一条边。

输出格式:
如果给定图是二分图,则输出 Yes,否则输出 No

数据范围:
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1n,m105

输入样例:

4 4
1 3
1 4
2 3
2 4

输出样例:

Yes

代码实现:

这里采用DFS实现,其实也可以用BFS实现。

#include<iostream>
#include<cstring>

using namespace std;

const int N = 1e5 + 10; // 定义最大顶点数
int h[N], e[N], ne[N], idx; // 邻接表相关数组
int n, m; // n为顶点数,m为边数
int color[N]; // 记录顶点颜色,初始化为0,表示未访问过

// 添加边的函数
void add(int a, int b)
{
    e[idx] = b; // 边的终点
    ne[idx] = h[a]; // 与顶点a相连的上一条边的编号
    h[a] = idx++; // h[a]存储与顶点a相连的最后一条边的编号
}

// 深度优先搜索函数,用于判断图是否为二分图
bool dfs(int u, int c)
{
    color[u] = c; // 将顶点u标记为颜色c
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i]; // 与顶点u相连的顶点j
        if (!color[j] && !dfs(j, 3 - c)) // 如果顶点j未被标记且与顶点u颜色相反,则递归调用dfs函数
            return false; // 如果递归返回false,说明不是二分图,直接返回false
        else if (color[j] == c) // 如果顶点j已被标记,并且颜色与顶点u相同,说明不是二分图,返回false
            return false;
    }
    return true; // 如果顶点u及其邻接点都符合二分图定义,则返回true
}

int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    memset(h, -1, sizeof h); // 初始化邻接表数组为-1
    cin >> n >> m; // 输入顶点数和边数
    for (int i = 0; i < m; ++i)
    {
        int a, b;
        cin >> a >> b; // 输入边的两个顶点
        add(a, b), add(b, a); // 添加无向边
    }
    bool flag = true; // 初始化标志位为true
    for (int i = 1; i <= n; ++i)
    {
        if (!color[i] && !dfs(i, 1)) // 对每个未被标记的顶点进行dfs,如果返回false,则不是二分图
        {
            flag = false; // 将标志位设为false
            break; // 直接跳出循环
        }
    }
    if (flag) // 如果标志位为true,输出"Yes"
        cout << "Yes" << endl;
    else // 否则输出"No"
        cout << "No" << endl;
    return 0;
}

tip:
妙用 3 − c 3 - c 3c 来表示与当前顶点相反的颜色。

扩展问题:

关押罪犯


匈牙利算法(计算二分图的最大匹配)

匈牙利算法(Hungarian algorithm)是用于解决二分图最大匹配问题。其基本思想是通过寻找增广路径来不断增加匹配的边数,直到无法找到新的增广路径为止。

二分图的匹配

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

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

匹配前:
在这里插入图片描述

匹配后:
在这里插入图片描述

算法步骤

下面是匈牙利算法的具体步骤:

  1. 初始化:将所有顶点的匹配状态置为未匹配( m a t c h match match 数组初始化为 0 0 0)。

  2. 遍历左侧顶点:对于二分图的每个左侧顶点i,开始寻找增广路径。

  • 将当前左侧顶点i的状态数组 s t st st 初始化为 f a l s e false false,用于标记增广路径中的顶点是否已经被访问。
  • 对当前左侧顶点 i i i,尝试在其邻接表中寻找一个未匹配的右侧顶点 j j j(如果存在),或者找到一个能够与右侧顶点 j j j 存在增广路径的未匹配顶点。
  • 若找到这样的顶点 j j j,则将右侧顶点 j j j 与左侧顶点i进行匹配(即将 m a t c h [ j ] match[j] match[j] 设置为 i i i),表示找到一条增广路径。
  1. 增加匹配数:在找到一条增广路径后,将匹配数 r e s res res 1 1 1

  2. 重复步骤 2 2 2 和步骤 3 3 3:重复执行上述步骤,直到无法找到新的增广路径为止。

  3. 输出结果:最终匹配的边数 r e s res res 即为二分图的最大匹配数。


算法应用

二分图的最大匹配

题目描述:
给定一个二分图,其中左半部包含 n 1 n_1 n1 个点(编号 1 ∼ n 1 1∼n_1 1n1),右半部包含 n 2 n_2 n2 个点(编号 1 ∼ n 2 1∼n_2 1n2),二分图共包含 m m m 条边。

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

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

输入格式:
第一行包含三个整数 n 1 、 n 2 和 m n_1、 n_2 和 m n1n2m

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

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

数据范围:
1 ≤ n 1 , n 2 ≤ 500 1≤n_1,n_2≤500 1n1,n2500
1 ≤ u ≤ n 1 1≤u≤n_1 1un1
1 ≤ v ≤ n 2 1≤v≤n_2 1vn2
1 ≤ m ≤ 1 0 5 1≤m≤10^5 1m105

输入样例:

2 2 4
1 1
1 2
2 1
2 2

输出样例:

2

代码实现:

#define _CRT_SECURE_NO_WARNINGS

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510, M = 1e5 + 10;
int n1, n2, m;
bool st[N]; // 数组用于在DFS过程中标记访问过的节点
int h[N], e[M], ne[M], idx, match[N]; // 图的表示和匹配数组

void add(int a, int b)
{
    e[idx] = b; // 将节点 'b' 添加到节点 'a' 的邻接表中
    ne[idx] = h[a]; // 更新节点 'a' 的邻接表指针
    h[a] = idx++; // 更新节点 'a' 的邻接表头指针,idx自增表示下一个插入的边的索引
}

bool find(int x)
{
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i]; // j为节点x的邻居节点
        if (st[j]) continue; // 如果节点j已经被访问过,则跳过
        st[j] = true; // 标记节点j为已访问
        if (match[j] == 0 || find(match[j])) // 如果节点j未匹配或者节点j的匹配节点能够寻找到增广路径
        {
            match[j] = x; // 将节点x匹配给节点j
            return true; // 返回成功匹配
        }
    }
    return false; // 无法找到增广路径,返回失败
}

int main()
{
    cin.tie(0); // 提高输入输出的效率
    ios::sync_with_stdio(false); // 关闭输入输出流的同步
    int res = 0; // 最大匹配数初始化为0
    memset(h, -1, sizeof h); // 初始化邻接表头指针为-1
    cin >> n1 >> n2 >> m; // 输入图的节点数和边数
    for (int i = 0; i < m; ++i)
    {
        int a, b;
        cin >> a >> b; // 输入图的边
        add(a, b); // 将边添加到邻接表中
    }
    for (int i = 1; i <= n1; ++i)
    {
        memset(st, false, sizeof st); // 每次匹配前都要将访问数组st重置为false
        if (find(i)) res++; // 尝试将节点i匹配,如果匹配成功,则最大匹配数加1
    }
    cout << res << endl; // 输出最大匹配数
}

扩展问题:

棋盘覆盖

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/801392.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jmeter并发测试

基本步骤 1、新建线程组 测试计划右键——>添加——>线程&#xff08;用户&#xff09;——>线程组 2、 添加HTTP请求 线程组右键——>添加——>取样器——>HTTP请求 3、 添加HTTP信息头管理器 线程组右键——>添加——>配置元件——>HTTP信息头…

【观察】以超融合创新架构,加速企业应用现代化

我们知道&#xff0c;数字化转型的不断加速&#xff0c;核心就是应用的加速。在整个数字化体系中&#xff0c;软件应用是让一切发挥价值的落地路径。在应用发挥能力之前&#xff0c;企业需要进行大量软硬件准备以及应用开发工作&#xff1b;在应用开始发挥能力之&#xff0c;企…

牛客小白月赛75F题解

文章目录 [ 打牌](https://ac.nowcoder.com/acm/contest/60063/F)问题建模问题分析代码 打牌 问题建模 给出三个长度为3的字符串&#xff0c;每个字符串仅由’w’,‘i’,‘n’三种字符组成&#xff0c;以及回合数n&#xff0c;每一个回合每一个字符串选择一个字符向其下一个字…

【Android】底层逻辑深入了解(学习笔记)(未完)

step by step. 目录 init启动 Zygote进程&#xff1a; SystemServer处理过程 Binder&#xff1a; Launcher启动过程 Android系统启动流程 四大组件 Activity Service BroadcastReceiver广播 ContentProvider内容提供者&#xff08;进程内和进程间的数据共享&#xff…

Latex | 使用MATLAB生成.eps矢量图并导入Latex中的方法

一、问题描述 用Latex时写paper时&#xff0c;要导入MATLAB生成的图进去 二、解决思路 &#xff08;1&#xff09;在MATLAB生成图片的窗口中&#xff0c;导出.eps矢量图 &#xff08;2&#xff09;把图上传到overleaf的目录 &#xff08;3&#xff09;在文中添加相应代码 三…

深度学习100例 | 第31天-卷积神经网络(DenseNet)识别生活物品

&#x1f680; 我的环境&#xff1a; 语言环境&#xff1a;Python3.6.5编译器&#xff1a;jupyter notebook深度学习环境&#xff1a;TensorFlow2.4.1显卡&#xff08;GPU&#xff09;&#xff1a;NVIDIA GeForce RTX 3080数据&#xff1a;&#x1f4cc;【传送门】 &#x1f…

Gradle和Maven的区别

Gradle和Maven 当涉及到构建和管理项目时&#xff0c;Gradle和Maven是两个非常流行的选项。本文将讨论Gradle和Maven之间的区别以及它们的配置信息差异。 1. Gradle和Maven的区别 1.1 构建脚本语言 Maven使用XML作为构建脚本语言&#xff0c;而Gradle使用基于Groovy的DSL&…

HashMap查找

文章目录 1 哈希表的基本概念1.1 两个例子1.2 如何查找1.3 若干术语 2 哈希函数的构造方法2.1 直接定址法2.2 除留余数法 3 处理冲突的方法3.1 开放地址法3.1.1 线性探测法3.1.2 二次探测法3.1.3 伪随机探测法 3.2 链地址法&#xff08;拉链法&#xff09;3.2.1 创建步骤3.2.2 …

STM32-风速传感器(ADC)

目录 0 说明 1 传感器介绍 2 代码说明 2.1 ADC.c 2.2 adc.h 2.3 main.c 0 说明 本篇文章主要是说明怎么使用STM32单片机读取风速传感器采集到的数据&#xff0c;读取方式是ADC&#xff0c;并且附带着STM32所需要的全部代码&#xff0c;所使用的风速传感器如下图所示。 附&am…

Redis—相关背景

Redis—相关背景 &#x1f50e;Redis—特性In-memory data structures—在内存中存储数据Programmability—可编程性Extensibility—可扩展性Persistence—持久化Clustering—集群High availability—高可用 &#x1f50e;Redis 为什么快&#x1f50e;Redis 的使用场景Real-tim…

看看ChatGPT的Embedding接口都完成哪些任务

调用Embedding接口完成文本分类 前面博客介绍了如何调用ChatGPT的Embedding接口完成文本聚类任务&#xff0c;实现过程入下图所示&#xff1a; 除了完成文本分类&#xff0c;调用Embedding接口还可完成聚类任务。 调用Embedding接口完成聚类任务 聚类任务是一种无监督学习任…

tinymce实现将word中内容(文字图片等)直接粘贴至编辑器中——利用插件tinymce-powerpaste-plugin

TinyMCE是一款易用、且功能强大的所见即所得的富文本编辑器。同类程序有&#xff1a;UEditor、Kindeditor、Simditor、CKEditor、wangEditor、Suneditor、froala等等。 TinyMCE的优势&#xff1a; 开源可商用&#xff0c;基于LGPL2.1 插件丰富&#xff0c;自带插件基本涵盖日常…

活字格性能优化技巧——如何利用数据库主键提升访问性能

大家都知道&#xff0c;活字格作为企业级低代码开发平台&#xff0c;拥有6大引擎&#xff0c;3大能力&#xff0c;能够高效落地企业级应用。在每年的应用大赛中也能看到很多格友利用活字格做了很多复杂的应用&#xff0c;例如2021年企业级低代码应用大赛中宁波聚轩利用活字格做…

vue使用qrcodejs2-fix或者qrcodejs2插件生成二维码

1. vue2安装 npm i qrcodejs2 1.1. vue3安装 npm install qrcodejs2-fix 2. 组件中引入并封装成公共组件&#xff0c;vue3版 <template><!-- 二维码生成 --><div class"body-div"><div style"width: 100%;height: 100%;" :id&quo…

誉天程序员-2301-3-day05

文章目录 知识回归1、单点登录SSO single sign on&#xff08;面试必考&#xff0c;10分&#xff09;2、Vue重大的扩展&#xff0c;Vue框架越来越完善&#xff0c;Vuex状态管理&#xff08;共享数据&#xff09; 全局守卫嵌套路由 知识回归 1、单点登录SSO single sign on&…

Vue2基础七、refnextTick自定义指令

零、文章目录 Vue2基础七、ref&nextTick&自定义指令 1、ref **作用&#xff1a;**利用 ref 和 $refs 可以用于 获取 dom 元素, 或 组件实例**特点&#xff1a;**查找范围 → 当前组件内 (更精确稳定)&#xff0c;用document.querySelect(‘.box’) 获取的是整个页面…

Ubuntu Server版 之 mysql 系列(-),安装、远程连接,mysql 创建用户、授权等

Ubuntu 分 桌面版 和 服务版 桌面版 &#xff1a;有额外的简易界面 服务版&#xff1a;是纯黑框的。没有任何UI界面的可言 安装mysql 安装位置 一般按照的位置存放在 /usr/bin 中 sudo apt-get install mysql-server查看mysql的状态 service mysql status mysql 安全设置…

【C语言课程设计】图书管理系统

引言&#xff1a; 图书管理系统是一个重要的信息管理系统&#xff0c;对于图书馆和书店等机构来说&#xff0c;它能够方便地管理图书的录入、显示、查询、修改和删除等操作。本实验基于C语言开发了一个简单的图书管理系统&#xff0c;通过账户名和密码进行系统访问和权限控制&a…

java数组对象初始化分析

分析代码 public static void main(String[] args) {int a10,b20,c30,d 40,e 50,f60;int aa[] {a,b,c,d,e,f};aa[2] 100;}代码的字节码 图解分析 refs https://docs.oracle.com/javase/specs/jvms/se19/html/jvms-6.html#jvms-6.5.aloadhttps://docs.oracle.com/javase/sp…

WebServer

socket是啥&#xff1f; 网络套接字&#xff08;Socket&#xff09;通常被表示为一个类或类似于类的数据结构。网络套接字类封装了网络通信的细节&#xff0c;并提供了用于建立、发送和接收网络数据的方法和属性。常见的成员有源端口&#xff0c;目标端口&#xff0c;源IP,目…