并查集解析

news2025/8/12 18:45:05

文章目录

  • 🚩并查集的理解
  • 🚩并查集的结构与原理
  • 🚩并查集的实现
    • 🍁整体框架
    • 🍁路径压缩
  • 🚩总结

🚩并查集的理解

并查集是基于数组操作的一个特殊数据结构,和以前学习[数组的堆排序]时有点相似,只不过这次的并查集用的是双亲描述法。我们知道数组的堆排序就是为了提高排序的效率,那么并查集是为了干什么呢?这里我先不讲并查集具体的数据结构,先引入一些日常的例子来说明并查集到底是干什么的。

就以老师给学生分组为例来理解。

假如有10个学生和1个老师,现在老师刚刚认识这些学生,由于不了解每个学生的具体状态和性格,就暂时没有给学生分组。那么此时的每个学生就相当于自己单独一个集合。过了一段时间之后,老师觉得自己有所了解学生们了,于是呢就将学生给分到了不同的小组。此时学生就形成了多个集合。又过了一段时间,老师认为之前分的小组不够彻底,于是又将一些小组给合并了起来。此时又完成了不同集合的合并,形成了新的集合群。现在呢,老师只需要任命一些学生作为这些不同小组的组长(代表),任命的时候就告诉非组长的学生他们的组长是谁,那么下次老师在找两个学生时,就能通询问他们的组长,从而知道这两个学生属不属于某个共同的小组。

image-20221124183252487

老师通过这种方式,就能把所有的学生发分成不同的集合来管理,完成逻辑上查询两个成员到底属于不属于同一个集合,而不用一个集合一个集合的去排查到底有没有同时拥有这两个学生。这是一种逆向管理的思维。

🚩并查集的结构与原理

上面提到,并查集就是对数组的一些操作,并且每个非集合代表的元素都知道自己的上一级所属组长是谁,那么是否可以这样思考:数组的下标与元素一一对应(映射)起来,然后数组刚开始都存-1,表示自己是一个集合的代表,并且成员个数就是 abs(-1)=1。之后只要合并某两个元素时,就将他们所在的集合A,B合并。此时假设B集合被合并到A集合中去,那么就将被集合B的根(集合代表,以下就统称根了)的数组内容改为集合A的根的下标,表示集合B的根已经不再是其成员的根了,集合A的根才是现在集合A与B的所有成员的根。所以还需要将集合A原来的元素个数加上集合B的元数个数才行。

上面巴拉巴拉的说了一大通,有些同学可能没看懂,没关系,我们画个图来辅助理解。

image-20221124201040902

上面的图可以看出每个成员都可以顺着数组内的下标找到自己所在集合的根节点,例:e[5] -> f[2] -> c[-6],即e属于c的集合

🚩并查集的实现

下面我用的是C++实现的,但其实和C语言差不多,主要是各个函数的实现的思想最重要。模板不同是语言造成的,并不影响大家学习,这点放心哈~

🍁整体框架

#include <iostream>
#include <vector>
using namespace std;
class UnionFindSet
{
private:
    vector<int> _ufs;  //就相当于一个数组,用来存放每个元素上一级节点的下标
public:
    UnionFindSet(size_t n)
        : _ufs(n, -1) //构造函数,在创建并查集的时候直接将数组_ufs初始化n个空间,并都复制为-1
    {
    }
    //找一个元素的根节点下标,x为元素的下标
    int FindRoot(int x)
    {
        if (x >= _ufs.size())  //越界查寻
        {
            cerr << "out of range" << endl;
            exit(2);
        }
        int root = x;     //初始化根节点为x,以防查找到元素的下标就是x
        while (_ufs[root] >= 0)//只要对应的数组内容>0就说明了还没找到,根节点的特征就是对应的数组内容<0
        {
            root = _ufs[root];  //继续向上一级查询
        }

        //查找的时候顺便压缩路径
        //使得被查找的成员与它上面的所有非根成员直接归属在根下面
        int parent;
        while (_ufs[x] >= 0)
        {
            parent = _ufs[x];//先保存上一级的下标
            _ufs[x] = root;  //将此时的节点直接链在根节点下
            x = parent;      //x更新成保存的上一级下标,检测上一级是否直接在根节点下了
        }
        return root;
    }

    //将两个集合联合起来
    void Union(int x1, int x2)
    {
        //找到两个元素对应的根的下标
        int root1 = FindRoot(x1);
        int root2 = FindRoot(x2);
        //同一个集合下的话就不做处理
        if (root1 == root2)
        {
            return;
        }
        //将较小的集合并在较大的集合中去,主要是尽量减少合并后找根的深度
        if (abs(_ufs[root1]) < abs(_ufs[root2]))
        {
            swap(root1, root2);
        }
        _ufs[root1] += _ufs[root2];
        _ufs[root2] = root1;
    }

    //两个成员是否在同一个集合中
    bool InSet(int x1, int x2)
    {
        return FindRoot(x1) == FindRoot(x2);//根对应的下标相同
    }

    //集合的个数,也就是数组中<0的个数
    int SetSize()
    {
        int n = 0;
        for (size_t i = 0; i < _ufs.size(); ++i)
        {
            if (_ufs[i] < 0)
            {
                ++n;
            }
        }
        return n;
    }

    //显示数组内的内容
    void Show()
    {
        for (size_t i = 0; i < _ufs.size(); ++i)
        {
            cout << _ufs[i] << " ";
        }
        cout << endl;
    }
};

⌨测试代码:

我就按照前面给的图进行测试了

void test_UnionFindSet()
{
    //abcdefghij
    UnionFindSet ufs(10);
    ufs.Union(2, 6);//c<-g
    ufs.Union(2, 1);//c<-b
    ufs.Union(7, 3);//h<-d
    ufs.Union(7, 0);//h<-a
    ufs.Union(7, 9);//h<-j
    ufs.Union(5, 4);//f<-e
    ufs.Union(5, 8);//f<-i
    ufs.Union(1, 5);//b<-f
    ufs.Show();
}
int main()
{
    test_UnionFindSet();
    return 0;
}

💻测试结果:

image-20221124205522555

与我们画的图的结果一模一样。

🍁路径压缩

实现的代码上面已经有了,我再给拿下来方便大家理解图解

//找一个元素的根节点下标,x为元素的下标(查找的过程中顺便实现路径的压缩)
    int FindRoot(int x)
    {
        if (x >= _ufs.size())  //越界查寻
        {
            cerr << "out of range" << endl;
            exit(2);
        }
        int root = x;     //初始化根节点为x,以防查找到元素的下标就是x
        while (_ufs[root] >= 0)//只要对应的数组内容>0就说明了还没找到,根节点的特征就是对应的数组内容<0
        {
            root = _ufs[root];  //继续向上一级查询
        }

        //查找的时候顺便压缩路径
        //使得被查找的成员与它上面的所有非根成员直接归属在根下面
        int parent;
        while (_ufs[x] >= 0)
        {
            parent = _ufs[x];//先保存上一级的下标
            _ufs[x] = root;  //将此时的节点直接链在根节点下
            x = parent;      //x更新成保存的上一级下标,检测上一级是否直接在根节点下了
        }
        return root;
    }

接下来来测试一下找根的过程的压缩处理:

void test_UnionFindSet()
{
    //abcdefghij
    UnionFindSet ufs(10);
    ufs.Union(2, 6);//c<-g
    ufs.Union(2, 1);//c<-b
    ufs.Union(7, 3);//h<-d
    ufs.Union(7, 0);//h<-a
    ufs.Union(7, 9);//h<-j
    ufs.Union(5, 4);//f<-e
    ufs.Union(5, 8);//f<-i
    ufs.Union(1, 5);//b<-f
    ufs.Show();
    ufs.Union(7,8);//将i与h联合起来
    ufs.FindRoot(8);//此时再找i对应的根,看压缩处理是否正确
    ufs.Show();
}
int main()
{
    test_UnionFindSet();
    return 0;
}

image-20221124212555705

压缩路径和预期的一样,逻辑正确。

🚩总结

并查集的学习重点在于将集合处理与数组操作联系在一起,难点在于将具体的集合问题抽象成数字的处理,实在不懂的话就自己动手画一画图,结合着上面的代码和截图自己跟着一步一步调试,总会搞明白的,也不是特别难哈,加油🐾~

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

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

相关文章

分析设备树文件

1.设备树是干嘛的 硬件资源有很多&#xff0c;想要实现分类管理&#xff0c;方便驱动去控制它&#xff0c;则需要设备树来管理硬件信息。 所以&#xff0c;设备树主要存放了一些设备节点信息&#xff0c;键值对&#xff0c;和属性&#xff1b;节点中也可以包含子节点。 2.设…

安全架构中的前端安全防护研究

国家互联网应急中心发布的被篡改网站数据让很多人触目惊心&#xff0c;近年来各种Web网站攻击事件频频发生&#xff0c;网站SQL注入&#xff0c;网页被篡改、信息失窃、甚至被利用成传播木马的载体Web安全形势日益严峻&#xff0c;越来越受到人们的关注。 Gartner 对安全架构的…

创建计划协议、维护创建计划、收货

创建计划协议事务码&#xff1a;ME31L创建计划协议 &#xff08;ME32L 修改计划协议 ME33L查询计划协议 ME2L查询采购订单&#xff09; 输入&#xff1a;供应商、协议类型、协议日期、采购组织、采购组、工厂、存储地点等信息后回车。 然后输入有效截至日期&#xff0c; 再点击…

计算机毕业设计java+springboot宠物商城系统

一、项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot Maven mybatis Vue 等等组成&#xff0c;B…

【JVM】垃圾回收:垃圾收集器

一、语境中的并行与并发 并行 并行描述的时多条垃圾收集器线程之间的关系&#xff0c;说明同一时间有多条这样的线程在协同工作&#xff0c;通常默认此时用户线程是处于等待状态。 并发 并发描述的是垃圾收集器线程与用户线程之间的关系&#xff0c;说明同一时间垃圾收集器线程…

简单实现一个虚拟形象系统

前言 上周启动居家开会的时候&#xff0c;看到有人通过「虚拟形象」功能&#xff0c;给自己带上了口罩、眼镜之类&#xff0c;于是想到了是不是也可以搞一个简单的虚拟形象系统。 大致想来&#xff0c;分为以下几个部分&#xff1a; 卷积神经网络(CNN) 下面讲解一下三层CN…

视频格式转换器哪个好用?万兴优转-好用的视频格式转换器

视频格式转换器是用于转换视频格式的软件&#xff0c;是指用于视频转换、音频转换、CD轨抓取、音视频混合转换、音视频剪切、连接转换、视频水印叠加、滚动字幕、个性化文字、图片叠加、视频相框叠加的音视频转换工具。 也就是说&#xff0c;视频有非常多的格式如AVI、VCD、SVC…

【JavaWeb从零到一】会话技术CookieSessionJSP

&#x1f680;【JavaWeb从零到一】系列文章目录 &#x1f6a9;【JavaWeb从零到一】前置知识 &#x1f6a9;【JavaWeb从零到一】Mysql基础总结 &#x1f6a9;【JavaWeb从零到一】JDBC详解 &#x1f6a9;【JavaWeb从零到一】JDBC连接池&JDBCTemplate Cookie&Session&…

王学岗音视频开发(二)—————OpenGLES开发实践

矩阵以及矩阵运算 上图就是m x a 的矩阵 1x30x22x1 :为左侧第一行乘以右侧第一列。 1x10x12x0 :为左侧第一行乘以右侧第二列。 -1x33x21x1:为左侧第二行乘以右侧第一列。 -1x13x11x0:为左侧第二行乘以右侧第二列。 矩阵的行列式 伴随矩阵 A*表示伴随矩阵 OpenGL 教程----屏…

Grails SpringBoot国际化不生效

问题描述&#xff1a; grails项目使用了国际化&#xff0c;按照官方文档的说法&#xff1a; 会根据用户浏览器访问时使用的Accept-Language头自动选择合适的语言。 但无论浏览器了配置什么语言甚至配置了Tomcat启动参数 -Duser.languagexxx -Duser.regionxxx页面显示依旧是英…

[附源码]java毕业设计一点到家小区微帮服务系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

QPushButton按钮用法

QPushButton 简介 QPushButton是一个很常用的一个按钮控件&#xff0c;主要用于创建一个可按压的按键。它显示了一 个文本和一个图标。另外&#xff0c;你也可以在创建时&#xff0c;指定一个快捷键。 基本用法 1. 创建 QPushButton主要有两种创建方法&#xff0c;一种是直…

SQLite实现的学生管理系统

SQLite数据库 案例资源所在地址&#xff1a; https://download.csdn.net/download/weixin_41957626/87150608?spm1001.2014.3001.5503 1.简介 1.1引入 1.前面学习的文件存储和SharedPreference存储的方式只能存储一些小型的数据但是对于复杂关系以及复杂数据结构的数据仅仅靠…

交互与前端16 Tabulator 表格实践4

说明 继续给表格来加一些小功能。 内容 1 分页 在表格初始化的地方加两行配置,表格就实现了分页 pagination:true, //enable.paginationSize:20, // this option can take any positive integer value2 超链接 这个需求的来源是,一些微服务需要注释,所以我写了很多文档…

科研教育「双目视觉技术」首选!维视MV-VS220双目立体视觉系统开发平台

NO.1产品背景 在最近大热的自动驾驶赛道&#xff0c;大疆采用新的技术路线——双目立体视觉。具体来说&#xff0c;它就是模拟人的视觉系统&#xff0c;通过两个临近摄像头所拍摄到的画面的视差&#xff0c;来还原出三维立体结构。不需要对海量数据进行标注和训练&#xff0c;可…

Echarts 散点象限图(二)动态绘制

之前发布过一篇文章Echarts散点象限图,基于死数据来绘制的,但实际开放场景中,需要请求数据,而且可能会动态更改数据,这时候需要如何处理,有什么要注意的地方,这篇文章详细说明一下。 主要需要处理的地方就是四个象限的markArea,需要根据中心的位置来画,你可以想象成…

36 - 经典问题解析三(赋值 string)

---- 整理自狄泰软件唐佐林老师课程 1. 关于赋值的疑问 什么时候需要重载赋值操作符&#xff1f;编译器是否提供默认的赋值操作&#xff1f; 编译器为每个类 默认重载了赋值操作符默认的赋值操作符 仅完成 浅拷贝当需要进行 深拷贝 时 必须重载赋值操作符赋值操作符与拷贝构造…

Elasticsearch系列【1】概述

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 文章目录Elastic公司Elastic StackElasticsearch结构化/非结构化数据全文搜索ES 发展史ES 特点ES 应用场景ES 应用案例Beats 系列LogstashKibanaElastic公司 Elastic是一家以搜索引擎闻名世界的软件公司…

pythoin爬虫2之利用cookie进行登录

python爬虫2之利用cookie进行登录利用requests模拟post方法cookies的获取session综合实例拓展&#xff1a;实现时间间隔&#xff08;第一次抓取后间隔...秒进行下一次抓取&#xff09;利用requests模拟post方法 requests.post(url,data,hearders) url即要解析的网址data即是向服…

【学习笔记18】JavaScript对象的基本认识

一、什么是对象 &#xff08;一&#xff09;概念 &#x1f603; 就是一个数据的集合(复杂数据)&#x1f601; 对象属于复杂数据类型(引用数据类型) &#xff08;二&#xff09;知识点的补充 JS 的数据类型 1. 基本数据 2. 复杂数据&#xff08;引用数据类型&#xff09; 引用数…