图论——并查集

参考内容:

图论——并查集(详细版)

并查集(Disjoint-set)是一种精巧的树形数据结构,它主要用于处理一些不相交集合的合并及查询问题。一些常见用途,比如求联通子图、求最小生成树的 Kruskal 算法和求最近公共祖先(LCA)等。

并查集的理念是只关注个体属于哪个阵营,并不关心这个阵营中个体内部的关系,比如我们常说的张三是李家沟的,王二是王家坝的。同时并查集借助个体代表集体的思想,用一个元素代表整个群体,就像我们开学都会有学生代表、教师代表讲话一样,在台上讲话的那一个学生就代表了学校所有的学生。

并查集基本操作

并查集的基本操作主要有初始化 init查询 find合并 union操作。

初始化

在使用并查集的时候,常常使用一个数组fa来存储每个元素的父节点,在一开始的时候所有元素与其它元素都没有任何关系,即大家相互之间还不认识,所以我们把每个元素的父节点设为自己。

#define ARR_LEN 6000

int fa[ARR_LEN];

void init(int n)
{
    for(int i = 1; i <= n; i++)
        fa[i] = i;
}

查询

查询即找到指定元素的祖先。需要注意的是,这里我们需要找到指定元素的根祖先,不能找到爸爸或者爷爷就停止了,而是要找到查找不下去了为止,所以要不断的去递归下去,直到找到父亲为自己的结点才结束。

int find(int i)
{
    if(i == fa[i]) // 递归出口
        return i;
    else
        return find(fa[i]); // 不断向上查找祖先
}

考虑下面的场景,假如第一次我们需要查询元素5的祖先,第二次需要查询元素4的祖先,会发现第一次查询包含了第二次查询的计算过程,但我们的程序却傻傻的计算了两次,有没有办法去来优化查询过程,让每一次查询都能利用到此前查询计算的便利?

考虑到并查集并不关心某个元素的爸爸、爷爷是谁,只关心最终的祖先是谁,所以我们可以在查询的过程中顺便做一些修改,比如在查询5的过程中,顺便就把42的父亲给修改为1,即我们在查找过程中进行路经压缩

int find(int i)
{
    if(i == fa[i]){
        return i;
    } else {
        fa[i] = find(fa[i]); // 进行路径压缩
        return fa[i];
    }
}

合并

合并操作即介绍两个人相互认识,将他们纳入同一个帮派,只需要将俩元素的父亲修改为同一个即可。

void union(int i, int j)
{
    int fa_i = find(i);
    int fa_j = find(j);
    fa[fa_i] = fa_j;
}

相关练习题目

洛谷 P1551 亲戚

题目连接:https://www.luogu.com.cn/problem/P1551

题目描述

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

规定: x x x y y y 是亲戚, y y y z z z 是亲戚,那么 x x x z z z 也是亲戚。如果 x , y x,y xy 是亲戚,那么 x x x 的亲戚都是 y y y 的亲戚, y y y 的亲戚也都是 x x x 的亲戚。

输入格式

第一行:三个整数 n , m , p , ( n , m , p ≤ 5000 ) n,m,p,(n,m,p≤5000) n,m,p(n,m,p5000) 分别表示有 n n n 个人, m m m 个亲戚关系,询问 p p p 对亲戚关系。

以下 m m m 行:每行两个数 M i , M j , 1 ≤ M i , M j ≤ n M_i,M_j,1≤M_i,M_j≤n MiMj1MiMjn,表示 M i M_i Mi M j M_j Mj 具有亲戚关系。

接下来 p p p 行:每行两个数 P i , P j P_i,P_j PiPj,询问 P i P_i Pi P j P_j Pj 是否具有亲戚关系。

输出格式

p p p 行,每行一个YesNo。表示第 i i i 个询问的答案为“具有”或“不具有”亲戚关系。

输入输出样例
# 输入
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

# 输出
Yes
Yes
No
题目解析

可以发现这是一个非常标准的并查集问题,简直和并查集模版如出一辙,因此直接将所有关系读取后进行合并,然后直接查询父亲是否为同一个即可。

#include<bits/stdc++.h>
using namespace std;

#define ARR_LEN 6000

int fa[ARR_LEN];

void init(int n)
{
    for(int i = 1; i <= n; i++)
        fa[i] = i;
}

int find(int i)
{
    if(i == fa[i]){
        return i;
    } else {
        fa[i] = find(fa[i]);
        return fa[i];
    }
}

void union(int i, int j)
{
    int fa_i = find(i);
    int fa_j = find(j);
    fa[fa_i] = fa_j;
}


int main()
{
    int n, m, p;
	int a, b;
	
	cin>> n >> m >> p;
	
	init(n);

	for(int i = 0; i < m; i++){
		cin >> a >> b;
		union(a, b);
	}
	
	for(int i = 0; i < p; i++){
		cin >> a >> b;
		int fa_a = find(a);
		int fa_b = find(b);
		
		if(fa_a == fa_b)
			cout<<"Yes"<<endl;
		else
			cout<<"No"<<endl;
	}
}

杭电 OJ1213 How Many Tables

题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1213

题目描述

Today is Ignatius’ birthday. He invites a lot of friends. Now it’s dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers.

One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table.

For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least.

输入格式

The input starts with an integer T ( 1 < = T < = 25 ) T(1<=T<=25) T(1<=T<=25) which indicate the number of test cases. Then T T T test cases follow. Each test case starts with two integers N N N and M ( 1 < = N , M < = 1000 ) M(1<=N,M<=1000) M(1<=N,M<=1000). N N N indicates the number of friends, the friends are marked from 1 1 1 to N N N. Then M M M lines follow. Each line consists of two integers A A A and B ( A ! = B ) B(A!=B) B(A!=B), that means friend A A A and friend B B B know each other. There will be a blank line between two cases.

输出格式

For each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks.

输入输出样例
# 输入
2
5 3
1 2
2 3
4 5

5 1
2 5

# 输出
2
4
题目解析

分析可以发现,这个问题要我们做的是统计在所有元素合并之后,统计总共有多个和集合。很轻松就能写出下面的 AC 代码。类似的问题还有杭电 OJ1232 畅通工程。

读者大人可以在此基础上继续进行延伸,我们实际生活中每个桌子只能坐 8 个人,假设还需要考虑每桌人数的容量,又如何进行改进呢?

#include<bits/stdc++.h>
using namespace std;

#define ARR_LEN 6000

int fa[ARR_LEN];

void init(int n)
{
    for(int i = 1; i <= n; i++)
        fa[i] = i;
}

int find(int i)
{
    if(i == fa[i]){
        return i;
    } else {
        fa[i] = find(fa[i]);
        return fa[i];
    }
}

void union(int i, int j)
{
    int fa_i = find(i);
    int fa_j = find(j);
    fa[fa_i] = fa_j;
}


int main()
{
    int n, m, a, b, t;

    cin>>t;
    for(int i = 0; i < t; i++){
        cin>>n>>m;
        int ans = 0;
        init(n);
        for(int i = 0; i < m; i++) {
            cin>>a>>b;
            union(a, b);
        }

        for(int i = 1; i <= n; i++) {
            // 如果父亲是自己,那么就表示一个独立的集合
            if(find(i) == i)
                ans++;
        }

        cout<<ans<<endl;
    }
    
}

杭电 OJ1272 小希的迷宫

题目连接:https://acm.hdu.edu.cn/showproblem.php?pid=1272

题目描述

小希设计了一个迷宫让 Gardon 玩,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间 A 和 B,那么既可以通过它从房间 A 走到房间 B,也可以通过它从房间 B 走到房间 A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从 5 到达 8。

输入格式

输入包含多组数据,每组数据是一个以 0 0 结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为 1,且不超过 100000。每两组数据之间有一个空行。整个文件以两个 -1 结尾。

输出格式

对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出Yes,否则输出No

输入输出样例
# 输入
6 8  5 3  5 2  6 4
5 6  0 0

8 1  7 3  6 2  8 9  7 5
7 4  7 8  7 6  0 0

3 8  6 8  6 4
5 3  5 6  5 2  0 0

-1 -1

# 输出
Yes
Yes
No
题目解析

其实这个问题就是让我们判断一个连通图中是否存在环,那么问题就转换为寻找出现环的条件。其实不难发现出现下面两种情况时,连通图即存在环。

  1. 在查找过程中,发现两个不同元素的父亲是相同的;
  2. 若不存在环,则边的数量一定比顶点数量少 1。
#include<bits/stdc++.h>
using namespace std;

#define ARR_LEN 100010

int fa[ARR_LEN];
bool visited[ARR_LEN]; // 用于辅助记录顶点的数量
int edges, points; // 记录顶点和边的数量
bool hascycle; // 是否存在环

void init()
{
    hascycle = false;
    edges = 0;
    points = 0;
    for(int i = 1; i < ARR_LEN; i++)
        fa[i] = i, visited[i] = false;
}

int find(int i)
{
    if(i == fa[i]){
        return i;
    } else {
        fa[i] = find(fa[i]);
        return fa[i];
    }
}

void union(int i, int j)
{
    int fa_i = find(i);
    int fa_j = find(j);

    // 两个元素祖先相同,存在环
    if(fa_i == fa_j) {
        hascycle = true;
    } else {
        visited[i] = true;
        visited[j] = true;
        edges++;
        fa[fa_i] = fa_j;
    }
}


int main()
{
    int a, b;

    init();
    
    while(cin>>a>>b) {
        if(a == 0 && b == 0) {
            cout<<"Yes"<<endl;
            continue;
        }

        if(a == -1 && b == -1) {
            return 0;
        }

        union(a, b);

        while(cin>>a>>b){
            if(a == 0 && b == 0) {
                break;
            }
            union(a, b);
        }

        if(hascycle) {
            cout<<"No"<<endl;
            continue;
        }

        for(int i = 1; i < ARR_LEN; i++){
            if(visited[i]) {
                points++;
            }
        }

        if(points == edges + 1) {
            cout<<"Yes"<<endl;
        } else {
            cout<<"No"<<endl;
        }
        init();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

测试接触不到第一手需求,如何保证不漏测?

测试接触不到第一手需求&#xff0c;了解到的需求都是分解过的需求&#xff0c;该怎么做才能保证不漏测&#xff1f; 这个问题还是挺普遍的。因为随着分工越来越精细&#xff0c;每个人可能只能接触到全局的一部分&#xff0c;再加上信息传递过程中的信息丢失&#xff0c;就很…

bootstrap3简单玩法

Bootstrap v3 Bootstrap v3 是一个流行的前端框架&#xff0c;它提供了一系列的模板、组件和工具&#xff0c;可以帮助开发者快速地构建响应式的网站和应用程序。 以下是 Bootstrap v3 的一些常见应用&#xff1a; 响应式布局&#xff1a;Bootstrap v3 提供了一个易于使用的网…

1.性能优化

概述 今日目标&#xff1a; 性能优化的终极目标是什么压力测试压力测试的指标 性能优化的终极目标是什么 用户体验 产品设计(非技术) 系统性能(快&#xff0c;3秒不能更久了) 后端&#xff1a;RT,TPS,并发数 影响因素01&#xff1a;数据库读写&#xff0c;RPC&#xff…

未来已来,“码”上见证---通义灵码

为了撰写一份关于通义灵码的产品测评&#xff0c;我将构建一个基于提供的产品介绍和评测内容要求的框架给大家介绍这款产品。 功能使用维度 代码智能生成 使用场景&#xff1a;开发中遇到需要编写新功能、单元测试、或对现有代码进行注释时。 使用效果&#xff1a;预期通义灵…

个体诊所管理系统电子处方软件,个体诊所人员服务软件,佳易王电子处方开单系统

个体诊所管理系统电子处方软件&#xff0c;个体诊所人员服务软件&#xff0c;佳易王电子处方开单系统 软件功能&#xff1a; 1、常用配方模板&#xff1a;可以自由添加配方分类&#xff0c;预先设置药品配方。 2、正常开药&#xff1a;可以灵活选择药品&#xff0c;用法用量&…

ubuntu| sudo apt-get update 更新失败, 没有 Release 文件 无法安全地用该源进行更新,所以默认禁用该源

xiaoleubt:~$ sudo apt-get update -y 命中:1 https://dl.google.com/linux/chrome/deb stable InRelease 忽略:2 http://ppa.launchpad.net/ubuntu-desktop/ubuntu-make/ubuntu focal InRelease 命中:3 https://packages.microsoft.com/repos/code stable InRelease 命中:4 ht…

老电脑升级内存、固态硬盘、重新装机过程记录

基础环境&#xff1a; 电脑型号&#xff1a;联想XiaoXin700-15ISK系统版本&#xff1a;Windows10 家庭中文版 版本22H2内存&#xff1a;硬盘&#xff1a; 升级想法&#xff1a; 内存升级&#xff0c;固态硬盘升级&#xff0c;系统重装&#xff08;干净一点&#xff09; 升级内存…

【java】实现自定义注解校验——方法一

自定义注解校验的实现步骤&#xff1a; 1.创建注解类&#xff0c;编写校验注解&#xff0c;即类似NotEmpty注解 2.编写自定义校验的逻辑实体类&#xff0c;编写具体的校验逻辑。(这个类可以实现ConstraintValidator这个接口&#xff0c;让注解用来校验) 3.开启使用自定义注解进…

超级英雄云计算的技术之旅

超级英雄云计算的技术之旅 超级英雄云计算的技术之旅摘要引言可变参数&#xff1a;Java的超级工具可变参数的用途1. 编写通用工具方法2. 构建日志记录工具3. 构建数据验证工具 云计算在智能家居中的应用1. 远程控制智能设备2. 数据分析和智能决策3. 安全和隐私4. 智能家居应用开…

掌动智能性能压力测试优势有哪些

企业通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。本文将介绍性能压力测试的价值及主要优势! 一、性能压力测试的价值 1、评估系统能力&#xff1a;有助于参数的基准测试&#xff0c;可以度量系统的响应时间;还有助于检查系统是否可…

python-opencv写入视频文件无法播放

python-opencv写入视频文件无法播放 在采用Python写OpenCV的视频时&#xff0c;生成的视频总是无法播放&#xff0c;大小只有不到两百k&#xff0c;播放器提示视频已经损坏。网上搜了一些方法&#xff0c;记录下解决办法。 代码如下 fourcc cv2.VideoWriter_fourcc(*MJPG) fp…

idea中配置spring boot单项目多端口启动

参照文章 https://zhuanlan.zhihu.com/p/610767685 项目配置如下 下面为 idea 2023&#xff0c;不同版本的设置有区别&#xff0c;但是没那么大&#xff0c;idea 2023默认使用新布局&#xff0c;切换为经典布局即可。 在项目根目录的.idea/workspace.xml文件里添加如下配置 &l…

装甲工程车3D虚拟云展厅提升企业在市场占有份额

应急通信车的出现&#xff0c;极大适应了防灾救援大数据背景下数字化、网络化、系统化、多维化的发展需求&#xff0c;为了让更多客户了解到应急通信车&#xff0c;提升企业在市场占有份额及领域&#xff0c;借助web3d开发制作的应急通信车3D云展示平台大大丰富了展示形式及内涵…

10年测试经验分享:新手如何找到适合自己的软件测试项目?

每一个测试新手&#xff08;特别是自学测试的人&#xff09;来说&#xff0c;往往不知道到哪里去找项目练手&#xff0c;这应该是最大的困扰了。 实话讲&#xff0c;这个目前没有非常好的、直接的解决办法&#xff0c;不过在这我可以结合我自己之前的一些工作经历&#xff0c;…

Linux实现进度条小程序(包含基础版本和模拟下载过程版本)

Linux实现进度条小程序[包含基础版本和模拟下载过程版本] Linux实现进度条小程序1.预备的两个小知识1.缓冲区1.缓冲区概念的引出2.缓冲区的概念 2.回车与换行1.小例子2.倒计时小程序 2.基础版进度条1.的回车方式的打印2.百分比的打印3.状态提示符的打印 3.升级版进度条1.设计:进…

webgoat-Insecure Deserialization不安全的序列化

A&#xff08;8&#xff09;不安全的反序列化 反序列化是将已序列化的数据还原回对象的过程。然而&#xff0c;如果反序列化是不安全的&#xff0c;那么恶意攻击者可以在序列化的数据中夹带恶意代码&#xff0c;从而在反序列化时执行这些代码。这种攻击被称为反序列化。 什么…

Java 开发常用的 Linux 命令

基本操作 Linux关机,重启 # 关机 shutdown -h now# 重启 shutdown -r now查看系统,CPU信息 # 查看系统内核信息 uname -a# 查看系统内核版本 cat /proc/version# 查看当前用户环境变量 envcat /proc/cpuinfo# 查看有几个逻辑cpu, 包括cpu型号 cat /proc/cpuinfo | grep name …

Linux 进程控制

进程地址空间的收尾 task_struct有一个结构体成员叫mm_struct&#xff0c;也就是进程地址空间。 为什么要有进程地址空间&#xff1a;进程内存地址管理&#xff0c;保护物理内存&#xff0c;进行权限审查&#xff0c;从无序变有序&#xff0c;让我们从统一的视角看待进程代码…

Java开发注意事项和细节说明

&#x1f468;‍&#x1f393;&#x1f468;‍&#x1f393;博主&#xff1a;发量不足 个人简介&#xff1a;耐心&#xff0c;自信来源于你强大的思想和知识基础&#xff01;&#xff01; &#x1f4d1;&#x1f4d1;本期更新内容&#xff1a;Java开发注意事项和细节说明&…

网络安全深入学习第八课——反向代理(工具:frp)

文章目录 一、实验环境二、实验要求三、开始模拟1、攻击机配置frp文件2、攻击拿下跳板机&#xff0c;并且上传frpc.ini、frpc.exe、frpc_full.ini文件3、把frps.ini、、frps.exe、frps_full.ini文件放到VPS主机上4、VPS机开启frp5、跳板机开启frp6、验证 一、实验环境 攻击机&…
最新文章