并查集介绍和常用模板
并查集介绍和常用模板
前言:
并查集(Union-find set 也叫Disjoint Sets)是图论里面一种用来判断节点之间是否连通的数据结构,学会使用它可以处理一些跟节点连通性的问题。它有两个很重要的方法:
-
Find(x):查找x的父元素
-
Union(x,y):将x,y两个对应的集合合并到一起
接下来我们先看一个例子,看看怎么判断节点之间是否相连,怎么把两个集合合并到一起:
先看第一个问题,怎么判断两个节点是否相连?
在上面这张图里面我们肉眼可以看到(0,1,2)是连在一起,(3,4)是连在一起,(5)是单独存在的,这也是前置客观存在的条件。
那么我们怎么判断0和3是不是连到一起了呢?思路是这样,我们把这三块看成是三个团队,(0,1,2)是一个团队,队长是0,(3,4)是一个团队,队长是3,(5)是一个团队,队长是5。判断逻辑是,两个元素的队长是相同的,就认为在一个团队里面。所以更加计算机化的语言描述是,三个集合,每个集合都有一个根节点,如果根节点是相同的,就是在一个集合里面。因此你要想到,我们需要存储每个节点对应的根节点,这样才能判断出两个节点是否在同一个集合内。
再看第二个问题,怎么将两个集合合并?
看过问题一后,你可能就已经有一些思路了,既然只要是根节点相同就是同一个集合,判断是否相连,那么我是不是合并两个集合的时候只要根节点改成同一个就行了?没错,就是这样。合并的时候就把某个集合的根节点改成另外一个集合的根节点就行了。这里引申出一个问题,应该把哪个集合的根节点修改掉?后面我们讲到按照秩来合并集合的时候会讲到,一般情况,随便用哪个集合的根节点都可以。
并查集常用模板:
1.QuickFind:
我们直接快进到代码部分,因为有了前面的例子的铺垫,再来讲代码应该就比较好理解了,说明我都放到代码的注释里面了。
public class UnionFind1 {// 首先我们创建了一个数组 用来保存每个元素的根节点public int root[];// 然后我们根据元素的数量 初始化数组,数组的下标就是节点,数组的值就是元素i的根节点,一开始每个元素的根节点都是自己public UnionFind1(int size) {root = new int[size];for (int i = 0; i < size; i++) {root[i] = i;}}// find找根节点,直接返回x对应的根节点root[x]public int find(int x) {return root[x];}// union合并两个元素x,ypublic void union(int x, int y) {// 先找到各自的根节点 一开始都是元素自己int rootX = find(x);int rootY = find(y);if (rootX != rootY) {// 因为两个根节点不一样 所以我们随便取一个元素的根节点作为新的根节点// 这里取的rootX为新的根节点,然后把原来所有根节点是rootY的都修改为rootXfor (int i = 0; i < root.length; i++) {if (root[i] == rootY) {root[i] = rootX;}}}};// 判断两个元素是否相连 其实就是判断根节点是否相同 是对find()的一种运用public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind1 uf = new UnionFind1(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}
这个模板为什么要叫QuickFind呢?因为这个模板的find方法比较快,是O(1)的时间复杂度,但是union方法就比较麻烦得元素都遍历一遍,复杂度是O(N)
2.QuickUnion
quickUnion主要是union的操作比较快,直接将x,y元素中任意一个元素变成另外一个元素的爸爸,操作是O(1),但是要找根节点的时候就毕竟慢,得一直往上找直到是根节点为止。
public class UnionFind2 {public int[] root;public UnionFind2(int size) {root = new int[size];for (int i = 0; i < size; i++) {root[i] = i;}}public int find(int x) {while (root[x] != x) {return root[x] = find(root[x]);}return x;}public void union(int x, int y) {int xRoot = find(x);int yRoot = find(y);if (xRoot != yRoot) {root[yRoot] = xRoot;}}public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind2 uf = new UnionFind2(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}
3.按秩合并的QuickUnion
public class UnionFind3 {// 在QuickUnion的基础上增加了rank数组来记录int root[];int rank[];// 初始化的时候rank默认都是1public UnionFind3(int size) {root = new int[size];rank = new int[size];for (int i = 0; i < size; i++) {root[i] = i;rank[i] = 1;}}public int find(int x) {while (x != root[x]) {x = root[x];}return x;}// 如果两个元素的秩相同就随机取一个元素为秩更大的 否则就是谁的秩大 谁就是爸爸public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {if (rank[rootX] > rank[rootY]) {root[rootY] = rootX;} else if (rank[rootX] < rank[rootY]) {root[rootX] = rootY;} else {root[rootY] = rootX;rank[rootX] += 1;}}}public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind3 uf = new UnionFind3(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}
4.路径压缩的QuickUnion
public class UnionFind4 {int root[];public UnionFind4(int size) {root = new int[size];for (int i = 0; i < size; i++) {root[i] = i;}}// 优化点在这里 再查找父节点的同时会把根节点赋值给递归过程的其他元素public int find(int x) {if (x == root[x]) {return x;}return root[x] = find(root[x]);}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {root[rootY] = rootX;}};public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind4 uf = new UnionFind4(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}
5.按秩Union并且路径压缩:
这个没有太多要说的,就是3,4模板和合并,可以同时解决查询慢和防止并查集变成链表的情况。
public class UnionFind5 {private int[] root;private int[] rank;public UnionFind5(int size) {root = new int[size];rank = new int[size];for (int i = 0; i < size; i++) {root[i] = i;rank[i] = 1;}}public int find(int x) {if (x == root[x]) {return x;}return root[x] = find(root[x]);}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {if (rank[rootX] > rank[rootY]) {root[rootY] = rootX;} else if (rank[rootX] < rank[rootY]) {root[rootX] = rootY;} else {root[rootY] = rootX;rank[rootX] += 1;}}}public boolean connected(int x, int y) {return find(x) == find(y);}public static void main(String[] args) throws Exception {// 下面的例子就更好理解了,我们先初始化并查集一共6个元素,就是上面图中的元素// 在图中的连接情况 把0,1,2连起来,3,4连起来,并用connected()测试下元素的连通性// 最后我们把元素5加入到1的集合中,测试下2和5的连通性,发现确实2和5也相连了,程序测试完成。UnionFind5 uf = new UnionFind5(6);// 0-1-2 3-4 5uf.union(0, 1);uf.union(0, 2);uf.union(3, 4);// trueSystem.out.println(uf.connected(1, 2));// falseSystem.out.println(uf.connected(1, 3));// falseSystem.out.println(uf.connected(4, 5));// 0-1-2 3-4 5uf.union(1, 5);// trueSystem.out.println(uf.connected(2, 5));}
}
用并查集解决问题:
这里我们来看一道用并查集解决的算法题leetcode-200岛屿数量
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。示例 1:输入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]
]
输出:1
示例 2:输入:grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]
]
输出:3提示:m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 '0' 或 '1'
这道题目可以用bfs/dfs解决,也可以用并查集,因为我们刚学习完并查集,所以就学以致用。
解题思路:
遍历整个图,将是邻近的岛屿元素全部用并查集连起来,也就是把1连起来,最后统计parent有多少个,就是有多少岛屿(不过这道题m*n比较大,遍历会超时)。当然我们也可以在union的时候判断出岛屿的数量,最后直接返回。
class UnionFind {public int root[];// 用来统计最后有多少个岛屿int num;public UnionFind(char[][] grid) {int row = grid.length;int col = grid[0].length;root = new int[row * col];for (int r = 0; r < row; r++) {for (int c = 0; c < col; c++) {if (grid[r][c] == '1') {// 如果是岛屿的话 num就增加num++;// 将二维坐标准换为一维的root[r * col + c] = r * col + c;}}}}// 优化点在这里 再查找父节点的同时会把根节点赋值给递归过程的其他元素public int find(int x) {if (x == root[x]) {return x;}return root[x] = find(root[x]);}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX != rootY) {root[rootY] = rootX;num--;}}}public int numIslands(char[][] grid) {int row = grid.length;int col = grid[0].length;UnionFind uf = new UnionFind(grid);int sea = 0;for (int r = 0; r < row; r++) {for (int c = 0; c < col; c++) {if (grid[r][c] == '1') {// 处理完之后 赋值为另外一个值 这样下次就不会重复遍历到这个值grid[r][c] = '0';if (r - 1 >= 0 && grid[r - 1][c] == '1') {uf.union(r * col + c, (r - 1) * col + c);}if (r + 1 < row && grid[r + 1][c] == '1') {uf.union(r * col + c, (r + 1) * col + c);}if (c - 1 >= 0 && grid[r][c - 1] == '1') {uf.union(r * col + c, r * col + c - 1);}if (c + 1 < col && grid[r][c + 1] == '1') {uf.union(r * col + c, r * col + c + 1);}}}}return uf.num;}@Testpublic void test1() {Assert.assertEquals(3, numIslands(new char[][]{new char[]{'1', '1', '0', '0', '0'},new char[]{'1', '1', '0', '0', '0'},new char[]{'0', '0', '1', '0', '0'},new char[]{'0', '0', '0', '1', '1'}}));Assert.assertEquals(1, numIslands(new char[][]{new char[]{'1', '1', '1'},new char[]{'0', '1', '0'},new char[]{'1', '1', '1'},}));}
相关文章:

并查集介绍和常用模板
并查集介绍和常用模板 前言: 并查集(Union-find set 也叫Disjoint Sets)是图论里面一种用来判断节点之间是否连通的数据结构,学会使用它可以处理一些跟节点连通性的问题。它有两个很重要的方法: Find(x):…...

解决deepspeed框架的bug:不保存调度器状态,模型训练重启时学习率从头开始
deepspeed存在一个bug,即在训练时不保存调度器状态,因此如果训练中断后再重新开始训练,调度器还是会从头开始而不是接着上一个checkpoint的调度器状态来训练。这个bug在deepspeed的github中也有其他人提出:https://github.com/mic…...

Linux ipc通信(消息对列)
前言:消息队列也是linux开发ipc机制中较为重要的一个进程间通信机制。 1.系统创建或获取消息对列 int msgget(key_t key, int mode); 创建消息队列,或者获取消息队列。 参数: key - 使用ftok()获取到的key mode - IPC_CREAT|0666 返回&…...

【计算机网络】 ARP协议和DNS协议
文章目录 数据包在传输过程中的变化过程单播组播和广播ARP协议ARP代理免费ARP路由数据转发过程DNS协议 数据包在传输过程中的变化过程 在说ARP和DNS之前,我们需要知道数据包在传输过程的变化过程 从图片中可以看到,发送方的原数据最开始是在应用层&…...

【逐步剖C++】-第一章-C++类和对象(上)
前言:本文主要介绍有关C入门需掌握的基础知识,包括但不限于以下几个方面,这里是文章导图: 本文较长,内容较多,大家可以根据需求跳转到自己感兴趣的部分,希望能对读者有一些帮助 那么本文也主要…...

索尼 toio™ 应用创意开发征文|探索创新的玩乐世界——索尼 toio™
导语: 在技术的不断进步和发展中,玩具也逐渐融入了智能化的潮流。索尼 toio™作为一款前沿的智能玩具,给孩子和成人带来了全新的游戏体验。本文将介绍索尼 toio™的特点、功能和应用场景,让读者了解这个令人兴奋的创新产品。 1. 了…...

企业架构LNMP学习笔记23
1、隐藏版本号: Nginx对外提供服务,为了避免被针对某个版本的漏洞进行攻击。经常做法是隐藏掉软件的版本信息,提供一定的安全性。 server_tokens off; https和CA: 1)基于SSL CA证书的公私钥的安全性。 CA是需要生成…...

第六章 图 五、图的深度优先遍历(DFS算法)
目录 一、定义 深度优先遍历通常用于解决以下问题: 深度优先遍历算法具有以下优点: 深度优先遍历算法的一个缺点是: 二、代码 空间复杂度: 时间复杂度: 邻接矩阵存储: 邻接表存储: 三、…...
React 中的 useLayoutEffect 钩子函数
useLayoutEffect钩子函数的作用跟useEffect钩子函数的作用一样,它们的不同主要是在于: 1、useEffect钩子函数是异步的,因为此函数在执行的时候是先计算出所有的 Dom 节点的改变后再将对应的 Dom 节点渲染到屏幕上,然而在 useEffe…...

upload-labs1-21关文件上传通关手册
upload-labs文件上传漏洞靶场 目录 upload-labs文件上传漏洞靶场第一关pass-01:第二关Pass-02第三关pass-03:第四关pass-04:第五关pass-05:第六关pass-06:第七关Pass-07第八关Pass-08第九关Pass-09第十关Pass-10第十一…...

MATLAB遗传算法求解生鲜货损制冷时间窗碳排放多成本车辆路径规划问题
MATLAB遗传算法求解生鲜货损制冷时间窗碳排放多成本车辆路径规划问题实例 1、问题描述 已知配送中心和需求门店的地理位置,并且已经获得各个门店的需求量。关于送货时间的要求,门店都有规定的时间窗,对于超过规定时间窗外的配送时间会产生相应的惩罚成本。为保持生鲜农产品的…...

界面控件DevExpress .NET应用安全 Web API v23.1亮点:支持Swagger模式
DevExpress拥有.NET开发需要的所有平台控件,包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具。 DevExpress 今年第一个重要版本v23.1日前已正式发布了,该版本拥有众多新产品和数十…...

SpringMVC之CRUD------增删改查
目录 前言 配置文件 pom.xml文件 web.xml文件 spring-context.xml spring-mvc.xml spring-MyBatis.xml jdbc.properties数据库配置文件 generatorConfig.xml log4j2日志文件 后台 PageBaen.java PageTag.java 切面类 biz层 定义一个接口 再写一个实现类 …...
微信小程序开发教学系列(4)- 抖音小程序组件开发
章节四:抖音小程序组件开发 在本章中,我们将深入探讨抖音小程序的组件开发。组件是抖音小程序中的基本构建块,它们负责展示数据和与用户交互。了解组件的开发方法和使用技巧是进行抖音小程序开发的重要一步。 4.1 抖音小程序的基本组件 抖…...
RabbitMQ反序列化失败:Failed to convert message
🎈 1 参考文档 RabbitMQ消费消息坑:failed to convert serialized Message content | jiuchengi-cnblogs 🔍2 问题描述 org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Failed to convert messageat org.sprin…...

CTFSHOW 年CTF
1.除夕 php的弱类型,用小数点绕过 这里后面直接加字母不行 2.初三 error_reporting(0); extract($_GET); include "flag.php"; highlight_file(__FILE__); 这里通过extract将get的参数导入为了变量 $_function($__,$___){return $__$___?$___:$__; }; …...

肖sir__设计测试用例方法之状态迁移法05_(黑盒测试)
设计测试用例方法之状态迁移法 一、状态迁移图 定义:通过描绘系统的状态及引起系统状态转换的事件,来表示系统的行为 案例: (1) 订机票案例1: l向航空公司打电话预定机票—>此时机票信息处于“完成”状…...

无涯教程-JavaScript - IMPRODUCT函数
描述 IMPRODUCT函数以x yi或x yj文本格式返回1到255个复数的乘积。两个复数的乘积为- $$(A BI)(C DI)(AC-BD)(A B)1 $$ 语法 IMPRODUCT (inumber1, [inumber2] ...)争论 Argument描述Required/OptionalInumber11 to 255 complex numbers to multiply.Required[inumbe…...

yapi以及gitlab的容器化部署
yapi部署: https://blog.csdn.net/Chimengmeng/article/details/132074922 gitlab部署 使用docker-compose.yml version: 3 services: web: image: twang2218/gitlab-ce-zh:10.5 restart: always hostname: 192.168.xx.xx environm…...
TCP、UDP 协议的区别,各自的应用场景
分析&回答 TCP 传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能&…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...