【高阶数据结构】并查集

文章目录

  • 1. 并查集原理
  • 2. 并查集的实现
  • 3. 并查集运用
    • 3.1 省份数量
    • 3.1.1 题目要求
    • 3.1.2 做题思路
    • 3.1.3 代码实现
    • 3.2 等式方程的可满足性
      • 3.2.1 题目要求
      • 3.2.2 做题思路
      • 3.2.3 代码实现

1. 并查集原理

在一些情况下,需要将 n 个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于哪个集合的运算。适合于描述这类问题的抽象数据类型称为并查集。

给大家举个例子:我在武汉上大学,但是我家在襄阳,每次来武汉上学的时候都需要坐高铁。在我第一次来学校的时候,是我一个人坐高铁来的,此时我就可以看成是一个单元素集合,但是到了大学之后,我认识了一些新朋友,并且我发现他们有的是襄阳的,有的是孝感的,有的就是武汉本地的,所以我们就约好了寒假来的时候襄阳的和襄阳的一起坐高铁过来,孝感的和孝感的,武汉的和武汉的一起来学校,这时候我们这几个人就组成了一个具有一定相同点的集合。

我用下面的图表示我和我的朋友们:

在这里插入图片描述
在上大学之前,我们都是单元素的集合,所以都用 -1 表示,我用 0 来表示,我的另外几个襄阳的朋友分别是 1、4、6,孝感的朋友是 2、5、8,武汉的朋友就是3、7、9,所以通过图形来表示的话就是:

在这里插入图片描述

在上面的树林中,每一棵树表示一个集合,这是使用树林给大家显示出来的关系,可以对于上面的数组,我们应该如何表现出这种关系呢?

在这里插入图片描述

  • 数组的下标对应集合中元素的编号
  • 数组中如果为负数,负号表示根,数字代表该集合中元素的个数
  • 数组中如果为非负数,代表该元素双亲在数组中的下标

从襄阳到武汉会经过孝感,所以我们襄阳的朋友和孝感的朋友约定到了孝感之后一起去学校,那么襄阳表示的集合就会和孝感表示的集合进行合并:

在这里插入图片描述

这样的话,我们数组对应下标的值也需要改变:

在这里插入图片描述
现在 0 集合中有 7 个元素,所以 -4 就要改为 -7,而 2 集合因为合并到了 0 集合,所以原来的根在数组中所表示的值就要改为 0,其他 2 集合的元素的根在数组中所表示的值也要改为 0。

通过上面的例子,我们可以知道并查集可以解决下面问题:

  1. 查找元素属于哪个集合
  • 沿着数组表示的树形结构往上一直找到根(树中元素为负数的位置)
  1. 查看两个元素是否属于同一个集合
  • 分别沿着数组表示的树形结构往上一直找到根,如果根相同,则表示在同一个集合中,不相同则表示不在同一个集合中
  1. 将两个不相同的集合归并为一个集合
  2. 求集合的个数
  • 遍历数组,数组中元素的值为负数的个数即为集合的个数

2. 并查集的实现

接下来,我们将学习如何实现并查集:

public class UnionFindSet {private int[] elem; //并查集的底层是一个数组public UnionFindSet(int n) {this.elem = new int[n]; //根据数据的多少创建适合大小的数组Arrays.fill(elem, -1); //开始的时候每个元素都是单元素的集合,所以我们数组每个元素的默认值都设置为-1}/*** 查找数据 x 的根节点下标* @param x* @return*/public int findRoot(int x) {if (x < 0) throw new ArrayIndexOutOfBoundsException("下标为负数,不合法");while (elem[x] >= 0) {x = elem[x];}return x;}/*** 判断x1和x2是否位于同一个集合* @param x1* @param x2* @return*/public boolean isSameUnionFindSet(int x1, int x2) {if (x1 < 0 || x2 < 0) throw new ArrayIndexOutOfBoundsException("下标为负数,不合法");int index1 = findRoot(x1);int index2 = findRoot(x2);//当x1的根节点和x2的根节点相同且不为-1的时候表明是一个集合if (index1 == index2 && index1 != -1) return true;return false;}/*** 将两个集合合并为一个集合* @param x1* @param x2*/public void union(int x1, int x2) {int index1 = findRoot(x1);int index2 = findRoot(x2);if (index1 == index2) return;elem[index1] = elem[index1] + elem[index2];elem[index2] = index1;}public int getCount() {int count = 0;for (int x : elem) {if (x < 0) count++;}return count;}
}

3. 并查集运用

3.1 省份数量

https://leetcode.cn/problems/number-of-provinces/

3.1.1 题目要求

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例 1:

在这里插入图片描述

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

示例 2:
在这里插入图片描述

输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

提示:

1 <= n <= 200
n == isConnected.length
n == isConnected[i].length
isConnected[i][j] 为 1 或 0
isConnected[i][i] == 1
isConnected[i][j] == isConnected[j][i]

3.1.2 做题思路

首先我们可以先遍历数组,遇到为 1 的位置,就说明 i 城市和 j 城市相连,那么我们就可以将这两个单元素的集合合并到一个元素,后面再遍历到 1 的时候,使用相同的操作进行集合的归并,这样最后直接相连和间接相连的城市就会在一个集合中。

3.1.3 代码实现

class Solution {public int findCircleNum(int[][] isConnected) {int len = isConnected.length;UnionFindSet ufs = new UnionFindSet(len);for (int i = 0; i < len; i++) {for (int j = 0; j < isConnected[0].length; j++) {if (isConnected[i][j] == 1) {ufs.union(i, j);}}}return ufs.getCount();}
}class UnionFindSet {private int[] elem; //并查集的底层是一个数组public UnionFindSet(int n) {this.elem = new int[n]; //根据数据的多少创建适合大小的数组Arrays.fill(elem, -1); //开始的时候每个元素都是单元素的集合,所以我们数组每个元素的默认值都设置为-1}/*** 查找数据 x 的根节点下标* @param x* @return*/public int findRoot(int x) {if (x < 0) throw new ArrayIndexOutOfBoundsException("下标为负数,不合法");while (elem[x] >= 0) {x = elem[x];}return x;}/*** 判断x1和x2是否位于同一个集合* @param x1* @param x2* @return*/public boolean isSameUnionFindSet(int x1, int x2) {if (x1 < 0 || x2 < 0) throw new ArrayIndexOutOfBoundsException("下标为负数,不合法");int index1 = findRoot(x1);int index2 = findRoot(x2);//当x1的根节点和x2的根节点相同且不为-1的时候表明是一个集合if (index1 == index2 && index1 != -1) return true;return false;}/*** 将两个集合合并为一个集合* @param x1* @param x2*/public void union(int x1, int x2) {int index1 = findRoot(x1);int index2 = findRoot(x2);if (index1 == index2) return;elem[index1] = elem[index1] + elem[index2];elem[index2] = index1;}public int getCount() {int count = 0;for (int x : elem) {if (x < 0) count++;}return count;}
}

在这里插入图片描述

3.2 等式方程的可满足性

https://leetcode.cn/problems/satisfiability-of-equality-equations/description/

3.2.1 题目要求

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:“a==b” 或 “a!=b”。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。

只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。

示例 1:

输入:["a==b","b!=a"]
输出:false
解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个			方程。没有办法分配变量同时满足这两个方程。

示例 2:

输入:["b==a","a==b"]
输出:true
解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。

示例 3:

输入:["a==b","b==c","a==c"]
输出:true

示例 4:

输入:["a==b","b!=c","c==a"]
输出:false

示例 5:

输入:["c==c","b==d","x!=z"]
输出:true

提示:

1 <= equations.length <= 500
equations[i].length == 4
equations[i][0] 和 equations[i][3] 是小写字母
equations[i][1] 要么是 '=',要么是 '!'
equations[i][2] 是 '='

3.2.2 做题思路

因为这道题目只有 == 和 != 关系,所以我们可以先遍历一遍数组,当字符串的 1 下标为 = 的时候,就说明是相等关系,我们就将这个字符串的 0 位置和 3 位置的数字给添加到一个集合中,这样,遍历一次之后,我们就可以把所有相同的数字给归并到一个集合中。然后我们再遍历一遍数组,这次当字符串的 1 下标为 ! 的时候,就说明是不等关系,那么我们只需要判断这两个元素是否在同一个集合中就可以了,如果在同一个集合中,就说明不符合,返回 false。

3.2.3 代码实现

class Solution {public boolean equationsPossible(String[] equations) {UnionFindSet ufs = new UnionFindSet(26);for (String s : equations) {if (s.charAt(1) == '=') {ufs.union(s.charAt(0) - 'a', s.charAt(3) - 'a');}}for (String s : equations) {if (s.charAt(1) == '!') {boolean flg = ufs.isSameUnionFindSet(s.charAt(0) - 'a', s.charAt(3) - 'a');if (flg) return false;}}return true;}
}

在这里插入图片描述

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

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

相关文章

【JVM篇】什么是jvm

文章目录 &#x1f354;什么是Java虚拟机&#x1f6f8;Java虚拟机有什么用&#x1f339;Java虚拟机的功能&#x1f388;Java虚拟机的组成 &#x1f354;什么是Java虚拟机 JVM指的是Java虚拟机&#xff0c;本质上是一个运行在计算机上的程序&#xff0c;可以运行 Java字节码文件…

20240212请问如何将B站下载的软字幕转换成为SRT格式?

20240212请问如何将B站下载的软字幕转换成为SRT格式&#xff1f; 2024/2/12 12:47 百度搜索&#xff1a;字幕 json 转 srt json srt https://blog.csdn.net/a_wh_white/article/details/120687363?share_token2640663e-f468-4737-9b55-73c808f5dcf0 https://blog.csdn.net/a_w…

springboot180基于spring boot的医院挂号就诊系统

医院挂号就诊系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装医院挂号就诊系统软件来发挥其…

基于STM32与FreeRTOS的四轴机械臂项目

目录 一、项目介绍 二、前期准备 1.硬件准备 2.开发环境 3.CubeMX配置 三、裸机各种模块测试 1.舵机模块 2.蓝牙模块 3.按键摇杆传感器模块和旋钮电位器模块 4.OLED模块 5.W25Q128模块 四、裸机三种控制测试 1.摇杆控制 2.示教器控制 3.蓝牙控制 五、裸机与Free…

权限系统设计

权限系统设计 RBAC 基于角色的访问控制 ABAC 基于属性的访问控制 普通的系统无非 CRUD&#xff0c;那系统如何控制一个用户该看到哪些数据、能操作哪些功能&#xff1f;日常开发中最常用到 RBAC 和 OAuth2 这两种访问控制和授权方案 RBAC 基于角色的访问控制 所有的访问控制模…

Visual Studio 2010+C#实现信源编码

1. 要求 本文设计了一套界面系统&#xff0c;该系统能够实现以下功能&#xff1a; 克劳夫特不等式的计算&#xff0c;并且能够根据计算结果给出相应的信息。可通过用户输入的初始条件然后给出哈夫曼编码以及LZ编码&#xff0c;结果均通过对话框来显示哈夫曼编码结果包含相应的…

【51单片机】利用【时间延迟】的原理规避【按键抖动问题】

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 本章是51LCD单片机设计的一个环节&#xff0c;完整可前往相应博客查看完整传送门 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下…

SPP改进(多窗口池化)

论文创新点汇总&#xff1a;人工智能论文通用创新点(持续更新中...)-CSDN博客 原来的模型 15年提出 本质&#xff1a; 多个不同大小的池化窗口进行池化 池化窗口越大得到的特征越少 之后再将不同池化窗口得到的特征拼接起来 现在的改进 实现代码 class SPPCSPC(nn.Modul…

【C基础刷题】第九讲

本系列博客为个人刷题思路分享&#xff0c;有需要借鉴即可。 1.目录大纲&#xff1a; 2.题目链接&#xff1a; 统计成绩 00&#xff1a;00&#xff1a;00⸺00&#xff1a;09&#xff1a;00题号&#xff1a;BC33 链接&#xff1a;https://www.nowcoder.com/practice/ cad8d94…

【JVM篇】什么是双亲委派机制

文章目录 &#x1f354;什么是双亲委派机制⭐案例 &#x1f339;双亲委派机制有什么用 &#x1f354;什么是双亲委派机制 指的是&#xff1a;当一个类加载器接收到加载类的任务的时候&#xff0c;会向上查找是否加载过&#xff0c;再从上到下进行加载 &#xff08;因为要上下查…

蓝桥杯题目解析 --汉诺塔(含C++栈的知识)

该题在蓝桥杯基础数据结构篇&#xff0c;进入后搜索题号1512。 汉诺塔 可以将圆盘临时置于B杆&#xff0c;也可以将A杆移除的圆盘重新移回A杆&#xff0c;但必须遵循上述两条规则。 汉诺塔了解后&#xff0c;题目也了解后&#xff0c;我们先通过试N的个数来找规律&#xff1a;…

Linux--目录结构

目录 一、Linux的目录结构二、常用的目录介绍 一、Linux的目录结构 Linux的目录结构是一个树型结构。 Windos 系统可以拥有多个盘符&#xff0c;如C盘&#xff0c;D盘,E盘。 Linux 木有盘符这个概念&#xff0c;只有一个根目录 /&#xff08;相当于文件夹&#xff09;&#xf…