深入浅出并查集(不相交集合实现思路)

引言

并查集(Disjoint Set Union,简称DSU)是一种用于处理一些不交集的合并及查询问题。它主要支持两种操作:查找(Find)和合并(Union)。

查找:确定某个元素属于哪一个子集。它可以用来确定两个元素是否属于同一子集。
合并:将两个子集合并成一个集合。
由于其实现简单、效率高,并查集被广泛应用于网络连接性问题、图像处理、社交网络分析等多个领域。本文将逐步介绍并查集的基本实现,并探讨其优化方法。

使用场景

并查集广泛应用于以下场景:

  1. 连通性问题:判断图中两个节点是否连通。例如,给定一张图,我们可以使用并查集来快速判断图中任意两点是否连通,或者计算整个图有多少个连通分量等。

  2. 最小生成树算法:如最小生成树Kruskal算法中用于判断是否形成环。

  3. 网络连接问题:判断网络中的两个节点是否连接。

  4. 社交网络:判断两个人是否属于同一个社交圈。

  5. 图像处理:图像处理中,用于连通区域标记。通过将相邻像素视为元素,并根据它们的颜色或灰度值决定是否合并,可以有效地识别出图像中的各个独立区域。

案例分析

引用自《算法第四版》

如图所示,0到9的10个数字,每个数字可以表示一个节点,节点之间可以连接起来,成为一个连通分量。我们需要一个数据结构,能够判断两个节点是否属于同一个连通分量(find),同时还可以把两个连通分量连在一起,形成一个更大的连通分量(union)

方案一quick-find

思路分析

我们可以考虑使用Map实现,其中key为节点的值,value为所在连通分量的标识。对于find方法直接使用get即可,而union方法,则需要将两个连通分量的标识统一即可,即找到两个节点的连通分量标识p和q, 将所有value=p的值替换为q(或者q替换为p)

实现步骤如图所示

引用自《算法第四版》

代码实现

这里使用数组代替map, 数组下表对应的节点的值,数组的value为连通分量标识

package uf;public class UnionFind {// 连通分量标识private final int[] parent;public UnionFind(int size) {parent = new int[size];}private int find(int n) {return parent[n];}private void union(int p, int q) {int rootP = find(p);int rootQ = find(q);// 更新p的连通分量标识为qif (rootP != rootQ) {for (int i = 0; i < parent.length; i++) {if (parent[i] == rootP) {parent[i] = rootQ;}}}}public static void main(String[] args) {UnionFind uf = new UnionFind(10);for (int i = 0; i < uf.parent.length; i++) {uf.parent[i] = i;}uf.union(4, 3);int uf4 = uf.find(4);int uf3 = uf.find(3);int uf8 = uf.find(8);System.out.println("uf4的parent: " + uf4 + ", uf3的parent:" + uf3 + ", uf8的parent:" + uf8);uf.union(3, 8);uf4 = uf.find(4);uf3 = uf.find(3);uf8 = uf.find(8);System.out.println("uf4的parent: " + uf4 + ", uf3的parent:" + uf3 + ", uf8的parent:" + uf8);}
}

结果如下

复杂度分析

  • 查找操作(Find):时间复杂度为O(1),因为我们直接访问数组中的元素。

  • 合并操作(Union):时间复杂度为O(n),因为我们需要遍历整个数组来更新所有属于某个集合的元素。

由于每个节点都指向父节点,这种扁平的结构使得find() 操作的速度显然是很快的,因为它只需要访问 id[] 数组一次, 因此这种算法称为quick-find。但 quick-find 算法一般 无法处理大型问题,因为对于每一对输入 union() 都需要扫描整个 id[] 数组。如果把0到9合并为一个连通分量,那么union的复杂度是平方级别的,那么应该如何改进呢?

方案二 quick-union

思路分析

可以这样思考,我们把扁平的结构变成每个分量用一棵树表示,树的根节点代表分量的代表元素。查找操作通过不断向上查找父节点,直到找到根节点;合并操作则将一棵树的根节点指向另一棵树的根节点。 这样union操作就能避免遍历整个数组了。这种算法叫做quick-union。
引用自《算法第四版》

代码实现

这里只需要改动find方法和union方法即可,原有的数据结构不用变
private int find(int n) {// 根节点的parent指向自己while (n != parent[n]) {// 不断向上查找父节点n = parent[n];}return n;}private void union(int p, int q) {int rootP = find(p);int rootQ = find(q);// 将p的根节点指向q的根节点if (rootP != rootQ) {parent[rootP] = rootQ;}}

复杂度分析

quick-union算法的union不用遍历整个数组了,但是算法的复杂度取决于输入的数据,因为构造的树不平衡

  • 查找操作(Find):时间复杂度为O(h),其中h是树的高度。在最坏情况下,树可能退化为链表,h = n,时间复杂度为O(n)。

  • 合并操作(Union):时间复杂度为O(h),与查找操作相同。

最坏的情况是变成了一个链表,那么find方法的复杂度变成了O(n)。那么我们该如何改进呢?

方案三 加权quick-union

思路分析

为了进一步优化树结构,我们可以引入加权规则:在合并两棵树时,将较小的树合并到较大的树上。这样可以有效减少树的高度,从而降低查找和合并操作的时间复杂度。这种数据结构需要提供一个额外的数组,用于存放根节点对应的连通分量的大小

引用自《算法第四版》

代码实现

package uf;public class UnionFind {// 父节点private final int[] parent;// 各个根节点所对应的分量的大小private final int[] size;public UnionFind(int size) {parent = new int[size];this.size = new int[size];}private int find(int n) {// 根节点的parent指向自己while (n != parent[n]) {// 不断向上查找父节点n = parent[n];}return n;}private void union(int p, int q) {int rootP = find(p);int rootQ = find(q);if (rootP != rootQ) {// 将分量较大的根节点指向分量较小的根节点if (size[p] < size[q]) {parent[rootP] = rootQ;size[rootQ] += size[rootP];} else {parent[rootQ] = rootP;size[rootP] += size[rootQ];}}}public static void main(String[] args) {UnionFind uf = new UnionFind(10);for (int i = 0; i < uf.parent.length; i++) {uf.parent[i] = i;}uf.union(4, 3);int uf4 = uf.find(4);int uf3 = uf.find(3);int uf8 = uf.find(8);System.out.println("uf4的parent: " + uf4 + ", uf3的parent:" + uf3 + ", uf8的parent:" + uf8);uf.union(3, 8);uf4 = uf.find(4);uf3 = uf.find(3);uf8 = uf.find(8);System.out.println("uf4的parent: " + uf4 + ", uf3的parent:" + uf3 + ", uf8的parent:" + uf8);}
}

可以看到结果是将小树的根连到了大树的根上

复杂度分析

  • 查找操作(Find):时间复杂度为O(log n),因为加权规则使得树的高度保持在O(log n)级别(这里不做证明,可以参考算法第四版)

  • 合并操作(Union):时间复杂度为O(log n),与查找操作相同。

加权树实现显著提高了并查集的效率,尤其是在处理大规模数据时。但是能不能让find方法的复杂度像quick-find一样快呢?

方案四 路径压缩

思路分析

我们尝试结合quick-find的扁平的数据结构,以及加权quick-union的平衡树的结构。在find方法中,在节点向上查询的过程中,顺便将路径中的节点的父节点指向root,这种操作叫做路径压缩。这样后续的find操作,对于压缩过路径的连通分量来说,可以使得树更扁平,提高find的效率。

假设我们有如下树结构

1
|  \
2   3
       \
        4

当我们调用 find(4) 时,路径压缩会将节点 4、3、2 直接指向根节点 1。
压缩后的树结构变为:

          1
        /  |  \   
      2  3   4

代码实现

这里是需要修改find方法,递归地修改每个父节点的parent

    private int find(int n) {while (n != parent[n]) {// 递归地将路径上的所有节点指向根节点parent[n]= find(parent[n]);}return n;}

 复杂度分析

  • 查找操作(Find):时间复杂度接近O(1),因为路径压缩使得树的高度几乎为常数。

  • 合并操作(Union):时间复杂度接近O(1),与查找操作相同。

总结

并查集是一种非常高效的数据结构,适用于处理不相交集合的合并与查找问题。通过从数组到树结构,再到加权树和路径压缩的演进,我们不断优化了并查集的性能。最终,路径压缩技术使得并查集的操作时间复杂度接近O(1),非常适合处理大规模数据。

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

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

相关文章

如何运行Composer安装PHP包 安装JWT库

1. 使用Composer Composer是PHP的依赖管理工具&#xff0c;它允许你轻松地安装和管理PHP包。对于JWT&#xff0c;你可以使用firebase/php-jwt这个库&#xff0c;这是由Firebase提供的官方库。 安装Composer&#xff08;如果你还没有安装的话&#xff09;&#xff1a; 访问Co…

享元模式——C++实现

目录 1. 享元模式简介 2. 代码示例 1. 享元模式简介 享元模式是一种结构型模式。 享元模式用于缓存共享对象&#xff0c;降低内存消耗。共享对象相同的部分&#xff0c;避免创建大量相同的对象&#xff0c;减少内存占用。 享元模式需要将对象分成内部状态和外部状态两个部分…

网络原理(4)—— 网络层详解

目录 一. IP协议报头结构 二. 地址管理 2.1 路由器 2.1.1 路由选择 2.1.2 WAN口&#xff08;Wide Area Network&#xff09; 2.1.3 LAN口&#xff08;Local Area Network&#xff09; 2.1.4 WLAN口&#xff08;Wireless Local Area Network&#xff09; 2.2 网段划分…

基于深度学习的输电线路缺陷检测算法研究(论文+源码)

输电线路关键部件的缺陷检测对于电网安全运行至关重要&#xff0c;传统方法存在效率低、准确性不高等问题。本研究探讨了利用深度学习技术进行输电线路关键组件的缺陷检测&#xff0c;目的是提升检测的效率与准确度。选用了YOLOv8模型作为基础&#xff0c;并通过加入CA注意力机…

Android --- handler详解

handler 理解 handler 是一套Android 消息传递机制&#xff0c;主要用于线程间通信。 tips&#xff1a; binder/socket 用于进程间通信。 参考&#xff1a; Android 进程间通信-CSDN博客 handler 就是主线程在起了一个子线程&#xff0c;子线程运行并生成message &#xff0c;l…

vim如何解决‘’文件非法关闭后,遗留交换文件‘’的问题

过程描述&#xff1a; 由于我修改文件时&#xff08;一定得修改了文件&#xff0c;不做任何修改不会产生这个问题&#xff09;的非法关闭&#xff0c;比如直接关闭虚拟机&#xff0c;或者直接断开远程工具的远程连接&#xff0c;产生了以下遗留交换文件的问题&#xff1a; 点击…

六十分之三十七——一转眼、时光飞逝

一、目标 明确可落地&#xff0c;对于自身执行完成需要一定的努力才可以完成的 1.第三版分组、激励、立体化权限、智能设备、AIPPT做课 2.8本书 3.得到&#xff1a;头条、吴军来信2、卓克科技参考3 4.总结思考 二、计划 科学规律的&#xff0c;要结合番茄工作法、快速阅读、…

Linux环境下的Java项目部署技巧:安装 Mysql

查看 myslq 是否安装&#xff1a; rpm -qa|grep mysql 如果已经安装&#xff0c;可执行命令来删除软件包&#xff1a; rpm -e --nodeps 包名 下载 repo 源&#xff1a; http://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm 执行命令安装 rpm 源(根据下载的…

css三角图标

案例三角&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><s…

Shadow DOM举例

这东西具有隔离效果&#xff0c;对于一些插件需要append一些div倒是不错的选择 <!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"utf-8"> <title>演示例子</title> </head> <body> <style&g…

万字长文深入浅出负载均衡器

前言 本篇博客主要分享Load Balancing&#xff08;负载均衡&#xff09;&#xff0c;将从以下方面循序渐进地全面展开阐述&#xff1a; 介绍什么是负载均衡介绍常见的负载均衡算法 负载均衡简介 初识负载均衡 负载均衡是系统设计中的一个关键组成部分&#xff0c;它有助于…

ChatGPT-4o和ChatGPT-4o mini的差异点

在人工智能领域&#xff0c;OpenAI再次引领创新潮流&#xff0c;近日正式发布了其最新模型——ChatGPT-4o及其经济实惠的小型版本ChatGPT-4o Mini。这两款模型虽同属于ChatGPT系列&#xff0c;但在性能、应用场景及成本上展现出显著的差异。本文将通过图文并茂的方式&#xff0…

双指针算法思想——OJ例题扩展算法解析思路

大家好&#xff01;上一期我发布了关于双指针的OJ平台上的典型例题思路解析&#xff0c;基于上一期的内容&#xff0c;我们这一期从其中内容扩展出来相似例题进行剖析和运用&#xff0c;一起来试一下吧&#xff01; 目录 一、 基于移动零的举一反三 题一&#xff1a;27. 移除…

WSL2中安装的ubuntu开启与关闭探讨

1. PC开机后&#xff0c;查询wsl状态 在cmd或者powersell中输入 wsl -l -vNAME STATE VERSION * Ubuntu Stopped 22. 从windows访问WSL2 wsl -l -vNAME STATE VERSION * Ubuntu Stopped 23. 在ubuntu中打开一个工作区后…

git笔记-简单入门

git笔记 git是一个分布式版本控制系统&#xff0c;它的优点有哪些呢&#xff1f;分为以下几个部分 与集中式的版本控制系统比起来&#xff0c;不用担心单点故障问题&#xff0c;只需要互相同步一下进度即可。支持离线编辑&#xff0c;每一个人都有一个完整的版本库。跨平台支持…

Chapter2 Amplifiers, Source followers Cascodes

Chapter2 Amplifiers, Source followers & Cascodes MOS单管根据输入输出, 可分为CS放大器, source follower和cascode 三种结构. Single-transistor amplifiers 这一章学习模拟电路基本单元-单管放大器 单管运放由Common-Source加上DC电流源组成. Avgm*Rds, gm和rds和…

LabVIEW透镜多参数自动检测系统

在现代制造业中&#xff0c;提升产品质量检测的自动化水平是提高生产效率和准确性的关键。本文介绍了一个基于LabVIEW的透镜多参数自动检测系统&#xff0c;该系统能够在单一工位上完成透镜的多项质量参数检测&#xff0c;并实现透镜的自动搬运与分选&#xff0c;极大地提升了检…

K8S集群部署--亲测好用

最近在自学K8S&#xff0c;花了三天最后终于成功部署一套K8S Cluster集群&#xff08;masternode1node2&#xff09; 在这里先分享一下具体的步骤&#xff0c;后续再更新其他的内容&#xff1a;例如部署期间遇到的问题及其解决办法。 部署步骤是英文写的&#xff0c;最近想练…

PHP实现混合加密方式,提高加密的安全性(代码解密)

代码1&#xff1a; <?php // 需要加密的内容 $plaintext 授权服务器拒绝连接;// 1. AES加密部分 $aesKey openssl_random_pseudo_bytes(32); // 生成256位AES密钥 $iv openssl_random_pseudo_bytes(16); // 生成128位IV// AES加密&#xff08;CBC模式&#xff09…

Linux - 进程间通信(3)

目录 3、解决遗留BUG -- 边关闭信道边回收进程 1&#xff09;解决方案 2&#xff09;两种方法相比较 4、命名管道 1&#xff09;理解命名管道 2&#xff09;创建命名管道 a. 命令行指令 b. 系统调用方法 3&#xff09;代码实现命名管道 构建类进行封装命名管道&#…