数据结构 —— 并查集

news2024/11/26 10:50:53

数据结构 —— 并查集

  • 并查集
  • 并查集实现
  • 省份数量
  • 并查集的优化

今天我们来了解一下并查集:

并查集

并查集(Disjoint Set Union, DSU)是一种高效的数据结构,用于处理集合的合并(Union)和查询(Find)操作。它的设计目的是快速判断两个元素是否属于同一个集合,并能够将多个集合合并成一个集合。并查集广泛应用于解决一些涉及分类、归属判断的问题,如图的连通性分析、社交网络的朋友圈划分、计算机网络中的域划分等场景。

并查集的核心思想围绕两个基本操作:

  1. 查找(Find):确定一个元素所属的集合。这一操作通常伴随着“路径压缩”优化,即在查找的过程中,将沿途经过的所有节点直接连接到它们的根节点,从而加速后续的查找操作。

  2. 合并(Union):将两个不同的集合合并成一个集合。为了保持数据结构的高效性,合并时往往采用按秩(每个集合中树的最大深度)或按大小(集合中元素的数量)的启发式合并策略,以避免树的高度过高,保证操作的近似对数时间复杂度。

并查集的典型应用场景包括:

  • 连通分量计数:在无向图中快速找出有多少个连通分量。
  • 判断连通性:给定两个顶点,判断它们是否在同一个连通分量内。
  • 最小生成树算法(如Kruskal算法)中的边的集合判断。
  • Tarjan算法中的强连通分量识别。
  • 动态集合划分问题,如组织管理、文件系统等。

并查集的魅力在于其简单直观的实现方式和高效的运行效率,尤其是在大数据量下的优秀表现。

并查集的操作其实就是合并两颗树
在这里插入图片描述
在这里插入图片描述同时我们这里可以直接运用数组数组里面存放指向父亲的父节点
在这里插入图片描述
现在我们来实现一个并查集:

并查集实现

我们把架子搭好:

#pragma once
#include<iostream>
#include<vector>

//并查集
class MyUnion
{
public:
    //初始化并查集
    MyUnion(size_t size)
      :_ufs(size,-1)
    {

    }

    //合并
    void Union(size_t number1,size_t number2)
    {

    }

    //寻根
    int FindRoot(size_t number)
    {

    }

    //判断是否在同一集合
    bool InSet(size_t number1,size_t number2)
    {
        
    }

    //共有几个集合
    size_t SetSize()
    {

    }



private:
    std::vector<int> _ufs;
};

我们这里最重要的函数就是要实现寻根,寻根函数是其他函数的基础。

比如我有这样的一个集合:
在这里插入图片描述现在我想把3合并到0那里:

肉眼看其实很简单,就是把3里面的数合并到0处,然后修改3里面的值,改为0(指向0)
在这里插入图片描述

这个时候0这个集合有两个数0,3。我们可以推出几个结论:

  1. 对应下标里面的数是负数,则这个数为
  2. 对应负数的绝对值表示这个集合里面的元素个数

这样寻根函数就很好写了,给定一个值,访问对应下标里面的数,若为负数则为根,否则迭代,直到为负数

    //寻根
    int FindRoot(size_t number)
    {
        int root = number;

        while(_ufs[root] >= 0)
        {
            root = _ufs[root];
        }

        return root;
    }

有了寻根函数,合并集合的操作也变的简单:

    //合并
    void Union(size_t number1,size_t number2)
    {
        int root1 = FindRoot(number1);
        int root2 = FindRoot(number2);

        if(root1 == root2)
            return;
				
		//这里让大树合并到小树上,树的高度更低,寻根次数会变低
        if(root1 < root2)
            std::swap(root1,root2);
            
		//父节点个数加上子集合中的个数
        _ufs[root1]+=_ufs[root2];
       //子节点指向父节点
        _ufs[root2] = root1;
    }

给出完整代码:

#pragma once
#include<iostream>
#include<vector>

// 并查集类定义
class MyUnion
{
public:
    // 构造函数,初始化并查集大小,每个元素初始时属于独立的集合,使用负数表示集合的大小(或作为父节点标记)
    MyUnion(size_t size)
      :_ufs(size, -1) // _ufs[i] 的值为负数表示 i 是一个集合的根,绝对值表示该集合的元素数量
    {
    }

    // 合并操作,将属于number1和number2的集合合并
    void Union(size_t number1, size_t number2)
    {
        // 分别找到number1和number2的根节点
        int root1 = FindRoot(number1);
        int root2 = FindRoot(number2);

        // 如果根相同,说明已经在同一集合内,无需合并
        if(root1 == root2)
            return;

        // 将较小深度的集合合并到较大深度的集合下,以保持树的平衡(此处通过比较根的值实现)
        if(root1 < root2)
            std::swap(root1, root2);

        // 合并:调整根节点的值以表示集合大小的变化
        _ufs[root1] += _ufs[root2]; 
        _ufs[root2] = root1; // 设置root2的父节点为root1
    }

    // 寻找给定元素的根节点,使用路径压缩优化
    int FindRoot(size_t number)
    {
        int root = number;

        // 路径压缩:直接将沿途所有节点指向根节点
        while(_ufs[root] >= 0)
        {
            root = _ufs[root];
        }

        return root;
    }

    // 判断两个元素是否属于同一集合
    bool InSet(size_t number1, size_t number2)
    {
        // 通过比较两元素的根节点是否相同来判断
        return FindRoot(number1) == FindRoot(number2);
    }

    // 返回并查集中集合的数量
    size_t SetSize()
    {
        size_t size = 0;
        // 遍历数组,负数表示集合的根,因此计数负数的个数即为集合的数量
        for(auto e : _ufs)
        {
            if(e < 0)
                size++;
        }

        return size;
    }

    // 打印并查集当前状态
    void Print()
    {
        for(auto e : _ufs)
        {
            std::cout << e << " ";
        }
        std::cout << std::endl;
    }

private:
    // 存储并查集数据的向量,_ufs[i]的值如果是负数表示i是集合的根,绝对值表示集合的大小;非负数表示其父节点的索引
    std::vector<int> _ufs;
};

我们来试试:

#include"MyUnion.h"

int main()
{
    MyUnion myUnion(23);

    myUnion.Union(12,2);
    myUnion.Union(12,7);
    myUnion.Union(12,0);
    myUnion.Union(8,4);

    myUnion.Print();

    return 0;
}

在这里插入图片描述

省份数量

力扣上面也有一道关于并查集的题:
在这里插入图片描述

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

这道题用并查集就很合适:

//并查集
class MyUnion
{
public:
    //初始化并查集
    MyUnion(int size)
      :_ufs(size,-1)
    {

    }

    //合并
    void Merege(int number1,int number2)
    {
        int root1 = FindRoot(number1);
        int root2 = FindRoot(number2);

        if(root1 == root2)
            return;

        _ufs[root1]+=_ufs[root2];
        _ufs[root2] = root1;
    }

    //寻根
    int FindRoot(int number)
    {
        int root = number;

        while(_ufs[root] >= 0)
        {
            root = _ufs[root];
        }

        return root;
    }

    //判断是否在同一集合
    bool InSet(int number1,int number2)
    {
        return FindRoot(number1) == FindRoot(number2);
    }

    //共有几个根
    size_t SetSize()
    {
        size_t size = 0;
        for(auto e : _ufs)
        {
            if(e < 0)
                size++;
        }

        return size;
    }

private:
    vector<int> _ufs;
};

class Solution 
{
public:
    int findCircleNum(vector<vector<int>>& isConnected) 
    {
        MyUnion myUnion(isConnected.size());

        for(int i = 0; i < isConnected.size(); i++)
        {
            for(int j = 0; j < isConnected[i].size(); j++)
            {
                if(isConnected[i][j]==1)
                {
                    myUnion.Merege(i,j);
                }
            }
        }

        return myUnion.SetSize();
    }
};

在这里插入图片描述

并查集的优化

我们这里主要介绍路径压缩:
目的:减少查询路径上的节点数量,使得后续的查询更快。

实现:在执行FindRoot操作时,不仅找到指定节点的根节点,还将沿途的所有节点直接连接到根节点。这样,之后对于这些节点的任何查询都将直接到达根,而无需再次遍历路径。

    // 寻根操作,采用路径压缩优化
    int FindRoot(size_t number)
    {
        int root = number;

        // 不断迭代直至找到根节点,根节点的.ufs值为负
        while(_ufs[root] >= 0)
        {
            root = _ufs[root]; // 循环直至到达集合的根
        }

        // 路径压缩:更新从number到根节点这条路径上的所有节点的父节点直接指向根节点
        // 这样下次查询这些节点时可以直接跳到根,减少查询时间
        while(number != root)
        {
            // 将当前节点的父节点设置为根节点
            int father = _ufs[number]; // 临时存储当前节点的父节点
            _ufs[number] = root;       // 将当前节点的父节点指向上一步找到的根节点
            number = father;           // 移动到下一个节点继续进行路径压缩
        }

        return root; // 最终返回根节点的索引
    }

在这里插入图片描述

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

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

相关文章

Linux应急响应靶机 2

一、靶机介绍 应急响应靶机-Linux2 前景需要&#xff1a;看监控的时候发现webshell告警&#xff0c;领导让你上机检查你可以救救安服仔吗&#xff01;&#xff01; 1,提交攻击者IP 2,提交攻击者修改的管理员密码(明文) 3,提交第一次Webshell的连接URL(http://xxx.xxx.xxx.…

使用MyBatis的动态SQL注解实现实体的CRUD操作

使用MyBatis的动态SQL注解实现实体的CRUD操作 1. 引言2. 准备工作2.1 创建数据表2.2 创建实体类 Book2.3 修改Mapper接口 BookMapper 3. 测试CRUD操作3.1 配置日志3.2 查询操作3.3 新增操作3.4 修改操作3.5 删除操作 4. 与MyBatis Plus的对比5. 动态SQL注解的适用场景5.1 动态查…

Python中相关软件安装

1. python安装 1.下载地址 https://www.python.org/downloads/2.选择安装版本 1. Anaconda安装 安装地址 -- 清华大学镜像站点 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/查看anaconda是否安装成功 2.conda安装好后&#xff0c;将镜像源修改为清华大学的镜像…

Python项目-文本语音转换器【附源码】

文本–>语音转换器 本项目是一个简单的从文本到语音这样一个转换器的图形用户界面应用&#xff0c;使用了Python的tkinter库来构建界面&#xff0c;以及pyttsx3库来执行转换。以下是对该项目代码的详细解释&#xff0c;后面会附上本项目的完整代码&#xff1a; 实现效果&am…

文本情绪指数与上证指数的VAR模型构建

大家好&#xff0c;我是带我去滑雪&#xff01; 在前一篇文章中&#xff0c;笔者爬取了东方财富网上证指数股吧的495775条评论数据&#xff0c;并对文本进行了情感分析&#xff0c;基于自制的股票情感词典&#xff0c;使用了深度学习模型对爬取的文本数据进行分类标注&#xff…

24V 350W开关电源电路原理图+PCB工程文件 UC3843AD lm193芯片

资料下载地址&#xff1a;24V 350W开关电源电路原理图PCB工程文件 UC3843AD lm193芯片 1、原理图 2、PCB

算法题 — 接雨水

给定 n 给非负整数&#xff0c;表示每个宽度为 1 的柱子的高度图&#xff0c;计算按照此排列的柱子&#xff0c;下雨之后能能接到多少雨水。 输入&#xff1a;height [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0, 1, 0, 2, 1,…

有了这5款在线去除图片水印工具,妈妈再也不用担心图片水印问题了

Inpaint Inpaint是一款功能强大的图像处理软件&#xff0c;主要用于去除图片中的水印、杂物等不需要的元素。它的主要功能包括&#xff1a; 去除水印&#xff1a;用户可以通过Inpaint轻松去除图片中的水印。只需在软件界面中打开带有水印的图片&#xff0c;然后使用标记工具选…

深入探索Java开发世界:Redis~类型分析大揭秘

文章目录 深入探索Java开发世界&#xff1a;Redis~类型分析大揭秘一、数据结构类型二、分布式锁类型三、事物命令类型四、事物三大特性类型 深入探索Java开发世界&#xff1a;Redis~类型分析大揭秘 Redis数据库基础知识&#xff0c;类型知识点梳理~ 一、数据结构类型 Redis是一…

Pycharm导入内置库或者第三方库时标红,no module named ‘xxx‘

各版本的Pycharm都有可能会出现这样的问题&#xff1a;有些时候内置库和第三方库被标红为“No module named xxx”&#xff0c;而自己的库却能被正常导入。 本人是在使用远程ssh解释器时遇到的。实际运行该代码文件时&#xff0c;能够正常运行&#xff08;若不能正常运行则可能…

Stop Motion Studio Pro for Mac:Mac上的动画大师,让你的创意无限流动!

Stop Motion Studio Pro for Mac为创作者们提供了一个直观且易于使用的平台&#xff0c;让他们能够将静态的物体和场景转化为生动有趣的定格动画。&#x1f3a5; 无论是制作简单的玩具动画&#xff0c;还是复杂的电影级场景&#xff0c;这款软件都能轻松应对&#xff0c;让你的…

浅谈Mysql Innodb存储引擎

一、Mysql整体架构 二、MySQL 5.7 支持的存储引擎 类型 描述 MyISAM 拥有较高的插入、查询速度&#xff0c;但不支持事务 InnoDB 5.5版本后Mysql的默认数据库&#xff0c;5.6版本后支持全文索引&#xff0c;事务型数据库的首选引擎&#xff0c;支持ACID事务&#xff0c;支…

真正的智慧——诺:九九归一,以简驭繁

一、九九归一 国学道家中有物极必反的理念&#xff0c;所以&#xff0c;中国人有九九归一的说法&#xff0c;在基本数字中&#xff0c;九是大数&#xff0c;九九之意&#xff0c;相当于后天八卦一样&#xff0c;相当于一个系统完成了一次大的循环&#xff0c;九九归一&#xf…

数据资产赋能企业决策:通过精准的数据分析和洞察,构建高效的数据资产解决方案,为企业提供决策支持,助力企业实现精准营销、风险管理、产品创新等目标,提升企业竞争力

一、引言 在信息化和数字化飞速发展的今天&#xff0c;数据已成为企业最宝贵的资产之一。数据资产不仅包含了企业的基本信息&#xff0c;还蕴含了丰富的市场趋势、消费者行为和潜在商机。如何通过精准的数据分析和洞察&#xff0c;构建高效的数据资产解决方案&#xff0c;为企…

【Confluence】markdown格式转换为Confluence

简单的文本可以使用网站来快速转换&#xff0c;但是发现很多格式不能正确转换&#xff0c;所以研究了一个Py的方法来实现&#xff0c;如下&#xff1a; 安装Py插件 本方法主要借用markdown2 来实现&#xff0c;开始之前需要先安装一些库。 pip install markdown2 beautiful…

葡萄串目标检测YoloV8——从Pytorch模型训练到C++部署

文章目录 软硬件准备数据准备数据处理脚本模型训练模型部署数据分享软硬件准备 训练端 PytorchultralyticsNvidia 3080Ti部署端 fastdeployonnxruntime数据准备 用labelimg进行数据标注 数据处理脚本 xml2yolo import os import glob import xml.etree.ElementTree as ETxm…

如何在写代码中找到乐趣

平时我们写代码呢&#xff0c;多数情况都是流水线式写代码&#xff0c;基本就可以实现业务逻辑了。 如何在写代码中找到乐趣呢&#xff0c;我觉得&#xff0c;最好的方式就是&#xff1a;使用设计模式优化自己的业务代码。 参考资料&#xff1a; 实战&#xff01;工作中常用到…

【C++进阶9】异常

一、C语言传统的处理错误的方式 终止程序&#xff0c;如assert 如发生内存错误&#xff0c;除0错误时就会终止程序返回错误码 需要程序员自己去查找对应的错误 z如系统的很多库的接口函数都是通 过把错误码放到errno中&#xff0c;表示错误 二、C异常概念 异常&#xff1a;函…

企业出海的浪潮下,如何利用亚马逊云(AWS)更好地应对?

在全球化的浪潮下&#xff0c;越来越多的企业开始将目光投向国际市场。在这个数字化时代&#xff0c;云计算技术成为企业出海的必备利器之一。AWS云作为全球领先的云服务提供商&#xff0c;凭借其卓越的性能和完善的服务体系&#xff0c;成为众多企业出海的首选。 一、出海为什…

【Mybatis 与 Spring】事务相关汇总

之前分享的几篇文章可以一起看&#xff0c;形成一个体系 【Mybatis】一级缓存与二级缓存源码分析与自定义二级缓存 【Spring】Spring事务相关源码分析 【Mybatis】Mybatis数据源与事务源码分析 Spring与Mybaitis融合 SpringManagedTransaction&#xff1a; org.mybatis.spri…