并查集(13张图解)--擒贼先擒王
目录
前言
故事
🌼思路
🌼总结
🌼代码
👊观察过程代码
👊正确代码
👊细节代码
来自《啊哈算法》
前言
刚学了树在优先队列中的应用--堆的实现
那么树还有哪些神奇的用法呢?我们从一个故事说起----揭秘犯罪团伙
故事
快过年了,犯罪分子们也开始为年终奖“奋斗”了,小哼的家乡出现多次抢劫事件。
由于强盗人数过于庞大,作案频繁,警察想调查清楚到底有几个犯罪团伙实在不容易。
不过警察叔叔还是搜集到了几条线索
现在有10个强盗,9条线索
1--2(表示1号强盗与2号强盗是同伙)
3--4,5--2,4--6,2--6,8--7,9--7,1--6,2--4
规定,强盗同伙的同伙也是同伙,你能帮警察叔叔查出有多少个独立的犯罪团伙吗?
🌼思路
要解决这个问题,先假设10个强盗相互不认识,他们各自为政,每个人都是自己的头,只听自己的
之后,我们通过警察的线索,一步步“合并同伙”
1,声明一维数组
申请一个一维数组dad,dad[1] == 1表示1号强盗的头是自己,10号强盗的头是10号自己用dad[10] == 10表示
2,初始化
初始化dad数组(当然,书里是f数组) ,令 dad[i] = i; 即可
3,合并同伙
如果发现两个强盗是同伙,那么他俩是同一犯罪团伙,但是。。。谁做老大呢?
我们假定左边的强盗更厉害些,规定一个“靠左法则”,比如警察得到的第一条线索是,1号和2号同伙
(1)
所以1号在左边,更厉害,那么2号归顺1号,也就是1号是2号的头头,所以dad[2] = 1;表示2号强盗的头是1号强盗
(2)
第二条线索,3--4(3号强盗和4号是同伙),根据“左边的更厉害”,4号归顺3号,所以 dad[4] = 3;
(3)
第3条线索,5--2,5号和2号强盗是同伙,dad[5] == 5,说明5号强盗的头是自己
dad[2] == 1,说明2号强盗的头是1号强盗
根据“左边的更厉害”,此时应该让2号归顺5号,那么“1号强盗”就不干了,“你凭什么抢我的人??”
于是这俩强盗差点打起来了,这让2号为难了,2号归顺5号还是继续跟着原来的头1号呢?
这里我们还是按左边的更厉害的原则,5号强盗直接找2号的头1号谈,让1号这个老大也归顺他(递归实现)
操作:dad[1] = 5; dad[2] = 5;
为什么在1号这个老大归顺5号后,还要再让2号归顺一次呢?
这一步不是必须的,但会提高后面找强盗最高领导人的速度(也就是找树的祖先的速度)
否则,很容易形成单支树结构(一长条。。。),极大浪费空间和时间
dad[2] = 5; 这看似多余的一步,也叫路径压缩(不要觉得名字很高大上,其实就是一行代码的事)
第四条线索
4--6(这俩同伙)
此时dad[4] == 3, dad[6] == 6,我们让6号强盗加入“3号犯罪团伙”,即dad[6] = 3;
第5条线索
2--6
此时dad[2] == 5, dad[6] == 3
我们令6号和他的老大都归顺“5号犯罪团伙”(递归实现),dad[6] = 5; 以及 dad[3] = 5;
第6条线索
8--7
此时dad[8] == 8, dad[7] == 7, 让7号归顺8号好了,dad[7] = 8;
第7条线索
9--7
此时dad[9] == 9, dad[7] == 8,所以8号和7号都归顺9号强盗(路径压缩)(递归实现)
dad[8] = 9; dad[7] = 9;
除了让7号的老大归顺9号,我们还让7号也直接归顺9号,这一步叫路径压缩,这一步通过递归返回时实现,不会增加时间复杂度
第8条线索
1--6
此时dad[1] == 5, dad[6] == 5,已经是同伙了,不需要操作
第9条线索
2--4
此时dad[2] == 5, dad[4] == 3
4号归顺3号,3号归顺5号,5号归顺自己,所以5号是最高领导人
从4号顺藤摸瓜到5号最高领导人的过程,就是递归
递归完后,dad[4] == 5;
至此,所有线索分析完毕!那么有多少个犯罪团伙呢?
由上图可知,3个团伙
5号犯罪集团:5,2,1,3,4,6组成
9号犯罪集团:9,8,7组成
10号犯罪集团:10组成
容易知道,如果 dad[i] == i,表示 i 号强盗是一个团伙的最高领导人,最后数组dad中:
dad[5] == 5, dad[9] == 9, dad[10] == 10,所以共3个犯罪团伙
🌼总结
我们刚才模拟的是并查集算法,并查集通过一个一维数组实现,本质是维护一个森林。
初始,森林每个点都是孤立的,也就是每个强盗的老大都是自己,也可以理解为每个点就是一棵只有一个节点的树
之后,将这些孤立的点合并成一颗大树,合并的过程就是叫爸爸的过程
叫爸爸的过程,遵循“左边更厉害”和“擒贼先擒王”的原则
“擒贼先擒王”也就是,先让老大归顺左边的强盗,自己再归顺(自己再归顺就是路径压缩)
补充: 并查集也称为为不相交集数据结构
🌼代码
👊观察过程代码
#include<iostream>
using namespace std;
int dad[100];int Find(int x) //不停认老大, 直到顶头上司
{if(dad[x] == x) return x;else {//路径压缩, 将路径上所有点的上级都设置为根节点dad[x] = Find(dad[x]);return dad[x];}
}void join(int x, int y) //合并两个团伙
{//m为x的最高领导人, n为y的最高领导人int m = Find(dad[x]), n = Find(dad[y]);if(m != n) //路径压缩dad[n] = m; //第2个最高领导人认第1个当老大
}int main()
{for(int i = 1; i <= 10; ++i)dad[i] = i; //初始化自己为老大int a, b;for(int i = 1; i <= 9; ++i) {cout<<"第"<<i<<"条线索:";cin>>a>>b;join(a, b); //合并团伙cout<<dad[a]<<" "<<dad[b]<<endl;}int ans = 0;for(int i = 1; i <= 10; ++i)if(dad[i] == i)ans += 1;return 0;
}
第1条线索:1 2
1 1
第2条线索:3 4
3 3
第3条线索:5 2
5 1
第4条线索:4 6
3 3
第5条线索:2 6
1 3
第6条线索:8 7
8 8
第7条线索:9 7
9 8
第8条线索:1 6
5 3
第9条线索:2 4
1 3
为什么第一次没实现路径压缩呢?(就是,虽然团伙总数确定了,但是每个人依然跟着自己原来的老大,没有跟着顶头上司)
原来是代码第18行
m = Find(dad[x]), n = Find(dad[y]);
应该改成
m = Find(x), n = Find(y);
否则只是令右老大归顺了左老大,但是右小弟没有直接归顺左老大,右小弟依然跟着右老大
这样就没起到路径压缩的效果
👊正确代码
即使是正确代码,《啊哈算法》里也存在BUG,不过无关痛痒而已(对时间复杂度几乎没影响)
#include<iostream>
using namespace std;
int dad[100];int Find(int x) //不停认老大, 直到顶头上司
{if(dad[x] == x) return x;else {//路径压缩, 将路径上所有点的上级都设置为根节点dad[x] = Find(dad[x]); //递归返回return dad[x];}
}void join(int x, int y) //合并两个团伙
{//m为x的最高领导人, n为y的最高领导人int m = Find(x), n = Find(y);if(m != n) //路径压缩dad[n] = m; //第2个最高领导人认第1个当老大
}int main()
{for(int i = 1; i <= 10; ++i)dad[i] = i; //初始化自己为老大int a, b;for(int i = 1; i <= 9; ++i) {cin>>a>>b;join(a, b); //合并团伙}int ans = 0;for(int i = 1; i <= 10; ++i)if(dad[i] == i)ans += 1;for(int i = 1; i <= 10; ++i)cout<<"第"<<i<<"个强盗的老大是"<<dad[i]<<endl;cout<<"共有"<<ans<<"个犯罪团伙";return 0;
}
1 2
3 4
5 2
4 6
2 6
8 7
9 7
1 6
2 4
第1个强盗的老大是5
第2个强盗的老大是5
第3个强盗的老大是5
第4个强盗的老大是5
第5个强盗的老大是5
第6个强盗的老大是5
第7个强盗的老大是8
第8个强盗的老大是9
第9个强盗的老大是9
第10个强盗的老大是10
共有3个犯罪团伙
上述代码实现了路径压缩,但是第7个强盗的老大怎么还是8呢,不应该是9吗
因为这种较为简单的路径压缩,有一个小小的缺陷:
需要第一次先找到祖宗,第二次才能对路径上的节点进行压缩,而输入9 7前,7原来的老大是8,8的原来的老大也是8;当输入9 7后,进行join()函数
此时7的老大8号就归顺了9号,但7的老大依然是8号,没有变成9号,需要在下一次递归查找老大时,7的老大才变成9号,但此时已经没有下次了
当然,我们也可以在join()函数的最后重写一次 n = Find(y);
就可以实现书里的效果,但是不知道这样是否会影响时间复杂度
👊细节代码
#include<iostream>
using namespace std;
int dad[100];int Find(int x) //不停认老大, 直到顶头上司
{if(dad[x] == x) return x;else {//路径压缩, 将路径上所有点的上级都设置为根节点dad[x] = Find(dad[x]); //递归返回return dad[x];}
}void join(int x, int y) //合并两个团伙
{//m为x的最高领导人, n为y的最高领导人int m = Find(x), n = Find(y);if(m != n) //路径压缩dad[n] = m; //第2个最高领导人认第1个当老大n = Find(y); //重写一次
}int main()
{for(int i = 1; i <= 10; ++i)dad[i] = i; //初始化自己为老大int a, b;for(int i = 1; i <= 9; ++i) {cin>>a>>b;join(a, b); //合并团伙}int ans = 0;for(int i = 1; i <= 10; ++i)if(dad[i] == i)ans += 1;for(int i = 1; i <= 10; ++i)cout<<"第"<<i<<"个强盗的老大是"<<dad[i]<<endl;cout<<"共有"<<ans<<"个犯罪团伙";return 0;
}
1 2
3 4
5 2
4 6
2 6
8 7
9 7
1 6
2 4
第1个强盗的老大是5
第2个强盗的老大是5
第3个强盗的老大是5
第4个强盗的老大是5
第5个强盗的老大是5
第6个强盗的老大是5
第7个强盗的老大是9
第8个强盗的老大是9
第9个强盗的老大是9
第10个强盗的老大是10
共有3个犯罪团伙
相关文章:

并查集(13张图解)--擒贼先擒王
目录 前言 故事 🌼思路 🌼总结 🌼代码 👊观察过程代码 👊正确代码 👊细节代码 来自《啊哈算法》 前言 刚学了树在优先队列中的应用--堆的实现 那么树还有哪些神奇的用法呢?我们从一…...

Flutter3引用原生播放器-IOS(Swift)篇
前言由于Flutter项目中需要使用到播放器功能,因此对flutter中各种播放器解决方案进行了一番研究和比对,最后决定还是自己通过Plugin的方法去引用原生播放器符合自己的需求,本篇文章会对各种解决方案做一个简单的比较,以及讲解一下…...

【蓝桥杯每日一题】双指针算法
🍎 博客主页:🌙披星戴月的贾维斯 🍎 欢迎关注:👍点赞🍃收藏🔥留言 🍇系列专栏:🌙 蓝桥杯 🌙我与杀戮之中绽放,亦如黎明的花…...

拼数(一般贪心)
链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 题号:NC16783 时间限制:C/C 1秒,其他语言2秒 空间限制:C/C 262144K,其他语言524288K 64bit IO Format: %lld 题目描述 设有n个正整…...

LeetCode 热题 C++ 169. 多数元素 10. 正则表达式匹配 155. 最小栈
力扣169 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入:nums [3,2,3] 输出࿱…...

Clickhouse学习:MergeTree
MergeTree一、MergeTree逻辑存储结构二、MergeTree物理存储结构三、总结一、MergeTree逻辑存储结构 如上图所示,在排序键(CountrID、Date)上做索引,数据会按照这两个字段先后排序ClickHouse是稀疏索引,每隔8192行做一个索引,如(a,1),(a,2),比如想查a,要读取[0,3)之间的内容,稀疏…...

【java基础】包装类,自动装箱和自动拆箱
文章目录基本介绍包装类自动装箱自动拆箱包装类注意事项包装类比较包装器内容不可变基本介绍 有时,需要将int这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。 例如,Integer类对应基本类型int。通常,这些类称为包装器&#…...

Android笔记(二十五):两种sdk热更插件资源加载方案
背景 在研究sdk插件化热更新方式的过程中总结出了两套插件资源加载方案,在此记录下 资源热更方式 方式一:合并所有插件资源 需要解决资源id冲突问题 资源ID值一共4个字段,由三部分组成:PackageIdTypeIdEntryId PackageId&…...

spring框架--全面详解(学习笔记)
目录 1.Spring是什么 2.Spring 框架特点 3.Spring体系结构 4.Spring开发环境搭建 5.spring中IOC和DI 6.Spring中bean的生命周期 7.Spring Bean作用域 8.spring注解开发 9.Spring框架中AOP(Aspect Oriented Programming) 10.AOP 实现分类 11.A…...

2023年CDGA考试模拟题库(401-500)
2023年CDGA考试模拟题库(401-500) 401.数据管理战略的SMART原则指的是哪项? [1分] A.具体 、高质量、可操作 、现实、有时间限制 B.具体、可衡量、可检验、现实、有时间限制 C.具体、可衡量、可操作、现实、有时间限制 D.具体、高质量、可检验、现实12-24个月的目标 答…...

软件设计师备考文档
cpu 计算机的基本硬件系统:运算器、控制器、存储器、输入设备、输出设备 cpu负责获取程序指令,对指令进行译码并加以执行 * cpu的功能控制器(保证程序正常执行,处理异常事件) 程序控制操作控制运算器(只能…...

Javascript的API基本内容(一)
一、获取DOM对象 querySelector 满足条件的第一个元素 querySelectorAll 满足条件的元素集合 返回伪数组 document.getElementById 专门获取元素类型节点,根据标签的 id 属性查找 二、操作元素内容 通过修改 DOM 的文本内容,动态改变网页的内容。 inn…...

10、最小公倍数
法一: #include <stdio.h>int main(){int a,b;scanf("%d%d",&a,&b);int m a>b?a:b;//m表示a,b之间的较大值while(1){if(m%a0&&m%b0){break;}m;}printf("%d",m);return 0; }法二:a*i%b0成立 #include &…...

【vue】vue2.x项目中使用md文件
一、Vue项目展示md文件的三种方式 1、将md文件 导入为 html 生成的标题标签自带具有id属性,值为标题内容; <h2 id"测试">测试</h2> # 处理md为html字符串 yarn add markdown-loader # 处理字符串,用于导出显示 yarn a…...

操作系统权限提升(十三)之绕过UAC提权-MSF和CS绕过UAC提权
系列文章 操作系统权限提升(十二)之绕过UAC提权-Windows UAC概述 注:阅读本编文章前,请先阅读系列文章,以免造成看不懂的情况!! MSF和CS绕过UAC提权 CS绕过UAC提权 拿到一个普通管理员的SHELL,在CS中没有*号代表有…...

快速排序+快速定位
快速排序算法采用了分治法以及递归作为解决问题的思想。在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以…...

nginx http rewrite module 详解
大家好,我是 17。 今天和大家聊聊 nginx http rewrite module 。 简单来说, ngx_http_rewrite_module module 用正则匹配请求,改写请求,然后做跳转。可以是内部跳转,也可以是外部跳转。 学习这个模块的时候…...

机器学习可解释性一(LIME)
随着深度学习的发展,越来越多的模型诞生,并且在训练集和测试集上的表现甚至于高于人类,但是深度学习一直被认为是一个黑盒模型,我们通俗的认为,经过训练后,机器学习到了数据中的特征,进而可以正…...

CV学习笔记-MobileNet
MobileNet 文章目录MobileNet1. MobileNet概述2. 深度可分离卷积(depthwise separable convolution)2.1 深度可分离卷积通俗理解2.2 深度可分离卷积对于参数的优化3. MobileNet网络结构4. 代码实现4.1 卷积块4.2 深度可分离卷积块4.3 MobileNet定义4.4 完…...

C++进阶——继承
C进阶——继承 1.继承的概念及定义 面向对象三大特性:封装、继承、多态。 概念: 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加功能,这…...

数据结构---单链表
专栏:数据结构 个人主页:HaiFan. 专栏简介:从零开始,数据结构!! 单链表前言顺序表的缺陷链表的概念以及结构链表接口实现打印链表中的元素SLTPrintphead->next!NULL和phead!NULL的区别开辟空间SLTNewNod…...

redis数据结构的底层实现
文章目录一.引言二.redis的特点三.Redis的数据结构a.字符串b.hashc.listd.sete.zset(有序集合)一.引言 redis是一个开源的使用C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value的NoSQL数据库。 通常使用redis作为缓存中间件来降低数据库的压力,除此…...

【JavaSE】复习(进阶)
文章目录1.final关键字2.常量3.抽象类3.1概括3.2 抽象方法4. 接口4.1 接口在开发中的作用4.2类型和类型之间的关系4.3抽象类和接口的区别5.包机制和import5.1 包机制5.2 import6.访问控制权限7.Object7.1 toString()7.2 equals()7.3 String类重写了toString和equals8.内部类8.1…...

Java 主流日志工具库
日志系统 java.util.logging (JUL) JDK1.4 开始,通过 java.util.logging 提供日志功能。虽然是官方自带的log lib,JUL的使用确不广泛。 JUL从JDK1.4 才开始加入(2002年),当时各种第三方log lib已经被广泛使用了JUL早期存在性能问题&#x…...

产品经理有必要考个 PMP吗?(含PMP资料)
现在基本上做产品的都有一个PMP证件,从结果导向来说,不对口不会有这么大范围的人来考,但是需要因地制宜,在公司内部里,标准程序并不流畅,产品和项目并不规范,关系错综复杂。 而产品经理的职能又…...

什么是原型、原型链?原型和原型链的作用
1、ES6之前,继承都用构造函数来实现;对象的继承,先申明一个对象,里面添加实例成员<!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title></head><body><script…...

条件期望4
条件期望例题----快排算法的分析 快速排序算法的递归定义如下: 有n个数(n≥2n\geq 2n≥2), 一开始随机选取一个数xix_ixi, 并将xix_ixi和其他n-1个数进行比较, 记SiS_iSi为比xix_ixi小的元素构成的集合, Siˉ\bar{S_i}Siˉ为比xix_ixi大的元素构成的集合, 然后分…...

网络协议分析(2)判断两个ip数据包是不是同一个数据包分片
一个节点收到两个IP包的首部如下:(1)45 00 05 dc 18 56 20 00 40 01 bb 12 c0 a8 00 01 c0 a8 00 67(2)45 00 00 15 18 56 00 b9 49 01 e0 20 c0 a8 00 01 c0 a8 00 67分析并判断这两个IP包是不是同一个数据报的分片&a…...

6.2 负反馈放大电路的四种基本组态
通常,引入交流负反馈的放大电路称为负反馈放大电路。 一、负反馈放大电路分析要点 如图6.2.1(a)所示电路中引入了交流负反馈,输出电压 uOu_OuO 的全部作为反馈电压作用于集成运放的反向输入端。在输入电压 uIu_IuI 不变的情况下,若由于…...

MySQL进阶之锁
锁是计算机中协调多个进程或线程并发访问资源的一种机制。在数据库中,除了传统的计算资源竞争之外,数据也是一种提供给许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决堆的一个问题,锁冲突也是影响数据…...