并并并并·病查坤

P1、什么是并查集

引用自百度百科:

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。

简单来说,并查集是一种以树形结构来表示不同种类数据的集合。一般当我们需要用到数据的连通性时会用到它。

并查集维护一个数组parent,parent数组中维护的不是元素本身,而是元素的下标索引,当然,这个下标索引是指向该元素的父元素的。

image-20240425223445850

引用自:【算法与数据结构】—— 并查集-CSDN博客

P2、简单、无优化的并查集

// 未改进版本
public class Djset {private int[] parent;  // 记录节点的根public Djset(int n) {for (int i = 0; i < n; i++) parent[i] = i;}public int find(int x) {if (x != parent[x]) return find(parent[x]);return parent[x];}public void merge(int x, int y) {int rootx = find(x);int rooty = find(y);if (rootX != rootY) {parent[rootY] = rootX;}}
}

这种没有任何优化的并查集,比较简单,但是效率很低。为什么?

问题1:

当合并两个节点时,这里是没有任何判断的,便直接将rootx设置成了rooty的父节点。

假如rooty的叶子节点深度比rootx的叶子节点深度大呢?此时,树的深度会持续增加,会造成后续的节点查询时间长。

问题2:

在寻找某个节点的根节点的过程中,我们也未对其父节点和祖父节点…等作任何操作。

假如该节点会被合并(merge)很多次,而每次都要经过父节点、祖父节点…层层寻找,造成了不必要的时间浪费。

比如:

image-20240425221005247

这样,树的深度便在无形之中增加了1。

如果反过来,将rootx的父节点设置为rooty,看下效果:

image-20240425220909242

树的深度是没有增加的,不会对后续节点造成影响。

P3、优化后的并查集【按秩合并】【路径压缩】

// 注意:使用该代码,并不能使得所有的元素都直接指向根节点,仍然存在间接的指向
public class Djset {private int[] parent;  // 记录节点的根private int[] rank;  // 记录根节点的深度,优化合并操作// 构造函数,初始化每个节点的根为其自身,并设置初始秩为0public Djset(int n) {parent = new int[n];rank = new int[n];for (int i = 0; i < n; i++) {parent[i] = i;rank[i] = 1; // 确保初始化每个节点的初始秩也为1}}// 查找x的根节点,同时进行路径压缩public int find(int x) {if (x != parent[x]) {parent[x] = find(parent[x]);  // 路径压缩至根节点}return parent[x];}// 合并x和y所在的集合,按秩合并优化public void merge(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {// 按秩合并:将秩较小的集合合并到较大的集合if (rank[rootX] < rank[rootY]) {int temp = rootX;rootX = rootY;rootY = temp;}parent[rootY] = rootX;  // 根据秩决定合并方向if (rank[rootX] == rank[rootY]) {rank[rootX]++;}}}
}

这里主要针对未改进的版本,做了两点优化 【按秩合并】和【路径压缩】

① 按秩合并

好的,让我们先弄清楚什么是按秩合并

秩:我们暂时将其定义为节点的最大深度(从节点自身开始,到叶子节点的最大深度)

按秩合并:主要是针对merge函数,在合并两个集合时,将秩大的根节点设置为秩小的根节点的父节点。意思是当要合并两个根节点A、B时,如果节点A的秩大于节点B的秩,那么将节点A设置为节点B的父节点,反之亦然。

比如:

image-20240425220909242

通过将秩大的根节点设置为合并后的根节点,避免了树的深度增加。

② 路径压缩

同样,先弄清楚什么是路径压缩

路径压缩:主要针对find函数,当在寻找一个节点A的根节点root时,直接将节点A的父节点B、祖父节点C…等节点全部指向根节点root。

优点:这样在下次寻找A的根节点、B的根节点、C的根节点时可以节省很长一段搜索路径。

如:

image-20240425221904252

接下来,来几个简单的困难题:

原题链接:

839. 相似字符串组 - 力扣(LeetCode)

image-20240426215746045

这个题目首先要理解相似的定义:

两个字符串相似的含义是能够通过交换两个字符的位置,得到另外一个字符串。

根据题目来看,给的字符串中的字母是相同的,不同的顺序,那么相似只有两种情况:两个字符串的对应位置中只有 0 个或者 2 个不同。

我们将字符串看作节点,两重 for 循环,实现对节点之间两两组合,判断两个节点是否相似,判断相似的方法是:

两个字符串的对应位置中只有 0 个或者 2 个不同;

如果两个字符串相似则将他们归入一组,之后遍历时,如果不是同一组就需要进行判断。

初始化并查集数组,并初始化集合数,每次合并减一。

class Solution {public static int[] father = new int[10000];public static int sets;public static void build(int n) {for (int i = 0; i < n; i++) {father[i] = i;}sets = n;}public static int find(int i) {if (i != father[i]) {father[i] = find(father[i]);}return father[i];}public static void union(int x, int y) {int fx = find(x);int fy = find(y);if (fx != fy) {father[fx] = fy;sets--;}}public int numSimilarGroups(String[] strs) {int n = strs.length;int m = strs[0].length();build(n);for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {if (find(i) != find(j)) {int diff = 0;for (int k = 0; k < m && diff < 3; k++) {//不同之处大于三那就没有意义了,一定不相似if (strs[i].charAt(k) != strs[j].charAt(k)) {diff++;}}if (diff == 0 || diff == 2) {union(i, j);}}}}return sets;}
}

原题链接:

765. 情侣牵手 - 力扣(LeetCode)

image-20240425204217850

首先,我们总是以「情侣对」为单位进行设想:

当有两对情侣相互坐错了位置,ta们两对之间形成了一个环。需要进行一次交换,使得每对情侣独立(相互牵手)

长度1的自环

如果三对情侣相互坐错了位置,ta们三对之间形成了一个环,需要进行两次交换,使得每对情侣独立(相互牵手)

image.png

如果四对情侣相互坐错了位置,ta们四对之间形成了一个环,需要进行三次交换,使得每对情侣独立(相互牵手)

也就是说,如果我们有 k 对情侣形成了错误环,需要交换 k - 1 次才能让情侣牵手。

于是问题转化成 n / 2 对情侣中,有多少个这样的环。

如果他们本来就是情侣,他们处于大小为1的错误环中,只需要交换0次。

如果不是情侣,说明他们呢两对处在同一个错误环中,我们将他们合并,将所有的错坐情侣合并后,答案就是情侣对 - 环个数。

class Solution {public int minSwapsCouples(int[] row) {int n = row.length;bulid(n / 2);for (int i = 0; i < n; i += 2) {union(row[i] / 2, row[i + 1] / 2);}return n / 2 - set;}//首先计算数组长度的一半(即情侣对数),然后调用bulid方法初始化并查集,//之后遍历数组,对每一对执行union操作合并它们所在的集合。// 最后,返回初始集合数量减去最终集合数量的结果,即为最少需要交换的次数。public static int[] father = new int[100];public static int set;public static void bulid(int x) {set = x;for (int i = 0; i < x; i++) {father[i] = i;}}public static int find(int i){if(i==father[i])return father[i];else return find(father[i]);}public static void union(int x,int y){if(find(x)!=find(y)){father[find(x)]=find(y);set--;}}
}

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

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

相关文章

银行押款车远程监控系统的实际需求与特点

随着金融行业的快速发展&#xff0c;银行押款车的安全性问题日益受到重视。传统的押款车监控方式已经无法满足现代安全管理的需求&#xff0c;因此&#xff0c;一种结合先进技术的远程监控系统应运而生。本文旨在探讨在运钞车上安装车载摄像机和集成有GPS、无线4G网络传输模块的…

C语言 switch语句

之前 我们讲了 if 和 嵌套的if分支语句 但其实 多分支语句 我们还可以用 switch 有时 switch 语句可以简化逻辑代码 switch语句也称之为开关语句&#xff0c;其像多路开关一样&#xff0c;使程序控制流程形成多个分支&#xff0c;根据一个表达式的不同取值&#xff0c;选择其…

社交巨头与去中心化:解析Facebook在区块链的角色

在数字化时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分。作为全球最大的社交媒体平台&#xff0c;Facebook 在社交领域的影响力无可置疑。然而&#xff0c;随着区块链技术的崛起&#xff0c;Facebook 也开始探索如何将这一技术应用于其平台&#xff0c;以适…

UE4_动画基础_FootIK

角色由于胶囊体的阻挡&#xff0c;双脚与地面平行&#xff0c;不会与斜坡、台阶等贴合&#xff0c;有一条腿会处于悬空状态&#xff0c;通过双骨骼IK节点使一只脚太高&#xff0c;让后胶囊体下降&#xff0c;修正双脚的角度。这就是逆向运动IK的方法。 一、新建第三人称模板游戏…

微信小程序开发:2.小程序组件

常用的视图容器类组件 View 普通的视图区域类似于div常用来进行布局效果 scroll-view 可以滚动的视图&#xff0c;常用来进行滚动列表区域 swiper and swiper-item 轮播图的容器组件和轮播图的item项目组件 View组件的基本使用 案例1 <view class"container"&…

【ARM 裸机】NXP 官方 SDK 使用

在前几节中&#xff0c;学习了如何编写汇编的 led 驱动、C 语言的 led 驱动、模仿 STM32 进行开发&#xff0c;我们都是自己写外设寄存器的结构体&#xff0c;外设非常的多&#xff0c;写起来费时费力&#xff0c;NXP 针对 I.MX6ULL 编写了一个 SDK 包&#xff0c;这个 SDK 包就…

解析Python中获取当前线程名字的方法及多线程编程实践

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python 获取当前线程的名字 在多线程编程中&#xff0c;了解当前线程的名字是一项重要的任…

SQL异常

异常 EXCEPTION 预定义异常 系统已经设置好的异常&#xff0c;包含了异常名&#xff0c;异常代码&#xff0c;异常信息组成 CASE NOT FOUND 未知异常&#xff1a;OTHERS 异常信息&#xff1a;SQLERRM 错误代码&#xff1a;SQLCODE 有各种各样的很多异常 捕获异常的语法 DE…

Redis高级篇详细讲解

0.今日菜单 Redis持久化【理解】 Redis主从 Redis哨兵 Redis分片集群【运维】 单点Redis的问题 数据丢失问题&#xff1a;Redis是内存存储&#xff0c;服务重启可能会丢失数据 并发能力问题&#xff1a;单节点Redis并发能力虽然不错&#xff0c;但也无法满足如618这样的高…

C++链表操作入门

数据结构基础&#xff1a;链表操作入门 数据结构基础&#xff1a;链表操作入门链表的基本概念链表的基本操作输出链表插入节点删除节点查找值 完整的链表操作示例结语 数据结构基础&#xff1a;链表操作入门 在计算机科学中&#xff0c;数据结构是组织和存储数据的方式&#x…

【Linux学习】​​学习Linux的准备工作和Linux的基本指令

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

C语言:插入排序

插入排序 1.解释2.步骤3.举例分析示例结果分析 1.解释 插入排序是一种简单直观的排序算法&#xff0c;它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上&#xff0c;通常采…

直播带货秘籍:人气飙升的成交话术大揭秘

在营销的广阔天地中&#xff0c;种草话术如同一把锐利的剑&#xff0c;能精准切入消费者的心智。选择恰当的切入点是关键&#xff0c;它可能是一个普遍的生活场景&#xff0c;一个共同的消费痛点&#xff0c;或是一处人们向往的心理寄托。通过细致的观察和分析&#xff0c;我们…

Markdown 列表语法

有序列表 要创建有序列表&#xff0c;请在每个列表项前添加数字并紧跟一个英文句点。数字不必按数学顺序排列&#xff0c;但是列表应当以数字 1 起始。 Markdown语法HTML预览效果1. First item 2. Second item 3. Third item 4. Fourth item<ol> <li>First item&…

Apollo 7周年大会:百度智能驾驶的展望与未来

本心、输入输出、结果 文章目录 Apollo 7周年大会&#xff1a;百度智能驾驶的展望与未来前言百度集团副总裁、智能驾驶事业群组总裁王云鹏发言 直播回放大会相关内容先了解 Apollo&#xfeff;开放平台 9.0架构图 发布产品Apollo 定义自己对于智能化的认知百度集团副总裁 王云鹏…

沉浸式推理乐趣:体验线上剧本杀小程序的魅力

在这个信息爆炸的时代&#xff0c;人们的娱乐方式也在不断地推陈出新。其中&#xff0c;线上剧本杀小程序以其独特的沉浸式推理乐趣&#xff0c;成为了许多人的新宠。它不仅让我们在闲暇之余享受到了推理的快乐&#xff0c;更让我们在虚拟的世界里感受到了人性的复杂与多彩。 线…

AI新闻速递:揭秘本周科技界最热的AI创新与发展

兄弟朋友们&#xff0c;本周的AI领域又迎来了一系列激动人心的进展。在这个快速变化的时代&#xff0c;不会利用AI的人&#xff0c;就像在数字化高速公路上步行的旅行者&#xff0c;眼看着同行者驾驶着智能汽车绝尘而去&#xff0c;而自己却束手无策。 1. Adobe Firefly 3&…

go 测试和文件

go 测试和文件 需求传统测试单元测试牛刀小试总结练习文件介绍打开关闭文件读文件一次性读取文件写文件文件或文件夹是否存在文件拷贝 需求 有一个函数&#xff0c;怎样确认他运行结果是正确的&#xff1f; func addUpper(n int)int {res : 0for i : 1; i < n; i {res1}r…

Matlab绘制对数轴

Matlab绘制对数轴 在Matlab中&#xff0c;可以使用semilogx和semilogy函数分别绘制对数坐标轴和线性坐标轴的图形&#xff0c;可以使用loglog绘制双对数轴图形。 使用semilogx函数绘制对数x轴的图形示例&#xff1a; x linspace(0.1, 10, 100); % 生成从0.1到10的100个等间隔…

【基础算法总结】双指针算法二

双指针 1.有效三角形的个数2.和为S的两个数字3.和为S的两个数字4.四数之和 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.有效三角形的个数…