并查集(13张图解)--擒贼先擒王

news2024/9/23 1:34:42

目录

前言

故事

🌼思路

🌼总结

🌼代码

👊观察过程代码

👊正确代码 

👊细节代码


来自《啊哈算法》

前言

刚学了树在优先队列中的应用--堆的实现

那么树还有哪些神奇的用法呢?我们从一个故事说起----揭秘犯罪团伙

故事

快过年了,犯罪分子们也开始为年终奖“奋斗”了,小哼的家乡出现多次抢劫事件。

由于强盗人数过于庞大,作案频繁,警察想调查清楚到底有几个犯罪团伙实在不容易。

不过警察叔叔还是搜集到了几条线索

现在有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个犯罪团伙

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

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

相关文章

前端卷算法系列(二)

前端卷算法系列&#xff08;二&#xff09; 回文数 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样…

zookeeper集群的搭建,菜鸟升级大神必看

一、下载安装zookeeperhttp://archive.apache.org/dist/zookeeper/下载最新版本2.8.1http://archive.apache.org/dist/zookeeper/zookeeper-3.8.1/二、上传安装包到服务器上并且解压&#xff0c;重命名tar -zxvf apache-zookeeper-3.8.1-bin.tar.gzmv apache-zookeeper-3.8.1-b…

设计环形队列

文章目录1.思路分析1.1队列空满分析1.2出队分析2.循环队列设计1.思路分析 1.1队列空满分析 首先我们假设一个长度为4的环形队列 队头front 队尾rear 当队列为空时 frontrear 当队列满时 frontrear 所以我们无法判断队列是满的或者空的 因此我们多加入一个空间使队列长度为5&am…

什么是自适应平台服务?

总目录链接==>> AutoSAR入门和实战系列总目录 文章目录 什么是自适应平台服务?1.1 自适应平台服务包含哪些功能簇呢?1.1.1 ara::sm 状态管理 (SM)1.1.2 ara::diag 诊断管理 (DM)1.1.3 ara::s2s 信号到服务映射1.1.4 ara::nm 网络管理 (NM)1.1.5 ara::ucm 更新和配置管…

数据结构期末复习总结(前章)

作者的话 作为一名计算机类的学生&#xff0c;我深知数据结构的重要性。在期末复习前&#xff0c;我希望通过这篇博客给大家一些复习建议。希望能帮助大家夯实数据结构的基础知识&#xff0c;并能够更好地掌握数据结构和算法的应用。 一、绪论 数据&#xff1a;信息的载体&am…

【测试】loadrunner安装

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录备注一、下载安装包二、安装loadrunner三、修改浏览器配置今天搬砖不努力&#xff0c;明天地位不稳定&#xff01; 备注 电脑最好有IE浏览器&#xff0c;但是没有也没事儿。&#xff08;注意&#xff1a;IE浏览器不…

Bootstrap系列之栅格系统

Bootstrap栅格系统 bootatrap提供了一套响应式&#xff0c;移动设备优先的流式网格系统&#xff0c;随着屏幕或者视口尺寸的增加&#xff0c;系统会自动分为最多12列&#xff0c;多出12列的将不再此行显示&#xff08;换行显示&#xff09; bootstrap网格系统有以下六个类 重点…

华为OD机试用Python实现 -【云短信平台优惠活动】(2023-Q1 新题)

华为OD机试题 华为OD机试300题大纲云短信平台优惠活动题目描述输入描述输出描述示例一输入输出说明示例二输入输出说明Python 代码实现代码编写思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看…

【Java基础】操作系统原理

一、进程 进程是指一段程序的执行过程&#xff0c;会消耗系统资源如CPU、内存、网络等。 一个进程包含静态代码段&#xff0c;数据&#xff0c;寄存器地址等 进程的特点 动态性&#xff08;可动态地创建、结束进程&#xff09; 并发性&#xff08;进程被独立调度并占用处理…

服务器部署—若依【vue】如何部署到nginx里面?nginx刷新页面404怎么办?【完美解决建议收藏】

服务器部署项目我们大家都会遇到&#xff0c;但是有些铁子会遇到很多的问题&#xff0c;比如前端部署nginx如何操作&#xff1f; 前端有单纯的静态页面、还有前后端分离的项目&#xff1b;这里博主直接分享最牛最到位的前后端分离项目的前端部署到nginx上面&#xff0c;以若依项…

C语言之习题练习集

&#x1f497; &#x1f497; 博客:小怡同学 &#x1f497; &#x1f497; 个人简介:编程小萌新 &#x1f497; &#x1f497; 如果博客对大家有用的话&#xff0c;请点赞关注再收藏 &#x1f31e; 文章目录牛客网题号&#xff1a; JZ17 打印从1到最大的n位数牛客网题号&#x…

Laravel框架03:DB类操作数据库

Laravel框架03&#xff1a;DB类操作数据库一、概述二、数据表的创建与配置三、增删改操作1. 增加信息2. 修改数据3. 删除数据四、查询操作1. 取出基本数据2. 取出单行数据3. 获取一个字段的值4. 获取多个字段的值5. 排序6. 分页五、执行任意的SQL语句一、概述 按照MVC的架构&a…

详讲函数知识

目录 1. 函数是什么&#xff1f; 2. C语言中函数的分类&#xff1a; 2.1 库函数&#xff1a; 2.2 自定义函数 函数的基本组成&#xff1a; 3. 函数的参数 3.1 实际参数&#xff08;实参&#xff09;&#xff1a; 3.2 形式参数&#xff08;形参&#xff09;&#xff1a; …

我那点浅薄的MOS模拟集成电路基础

记录研究生课程模拟集成电路设计所学到的一些知识&#xff0c;这门课是由刘老师和周老师一起上的&#xff0c;刘老师讲模拟集成部分这个模集跟模电还是有很大的区别的&#xff0c;模拟集成主要是针对MOS器件的集成&#xff0c;学得更专业也更深&#xff1b;而周老师讲的是信号检…

华为OD机试题,用 Java 解【图片整理】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

Yolov3,v4,v5区别

网络区别就不说了&#xff0c;ipad笔记记录了&#xff0c;这里只说其他的区别1 输入区别1.1 yolov3没什么特别的数据增强方式1.2 yolov4Mosaic数据增强Yolov4中使用的Mosaic是参考2019年底提出的CutMix数据增强的方式&#xff0c;但CutMix只使用了两张图片进行拼接&#xff0c;…

前端组件库自定义主题切换探索-02-webpack-theme-color-replacer webpack 的实现逻辑和原理-02

接上一篇&#xff1a;《前端组件库自定义主题切换探索-02-webpack-theme-color-replacer webpack 的实现逻辑和原理-01》 上一篇说到&#xff0c;在Handler.js的this.options下面的代码&#xff0c;this.assetsExtractor new AssetsExtractor(this.options)&#xff0c;表明op…

【C++】类与对象理解和学习(下)

放在专栏【C知识总结】&#xff0c;会持续更新&#xff0c;期待支持&#x1f339;建议先看完【C】类与对象理解和学习&#xff08;上&#xff09;【C】类与对象理解和学习&#xff08;中&#xff09;本章知识点概括Ⅰ本章知识点概括Ⅱ初始化列表前言在上一篇文章中&#xff0c;…

笔记本一锁屏程序就结束(锁屏程序结束、锁屏程序退出)(在此时间后关闭硬盘、硬盘关闭)(计算机空闲状态)

笔记本一锁屏程序就结束原因问题背景问题原因在此时间后关闭硬盘何为“空闲状态”&#xff1f;解决办法问题背景 我用向日葵开了个远程连接我家里的电脑&#xff0c;但是我的笔记本一锁屏&#xff0c;过了一会回来再打开&#xff0c;向日葵就自动结束了&#xff0c;不知道咋回…

解决前端组件下拉框选择功能失效问题

问题&#xff1a; 页面下拉框选择功能失效 现象&#xff1a; 在下拉框有默认值的情况下&#xff0c;点击下拉框的其他值&#xff0c;发现并没有切换到其他值 但是在下拉框没默认值的情况下&#xff0c;功能就正常 原因 select 已经绑定选项&#xff08;有默认值&#xff09; 在…