【算法与数据结构】并查集详解+题目

news2025/3/24 12:53:39

目录

一,什么是并查集

二,并查集的结构 

三,并查集的代码实现 

1,并查集的大致结构和初始化

2,find操作 

3,Union操作

4,优化 

小结:

四,并查集的应用场景

省份数量[OJ题] 


一,什么是并查集

核心概念:并查集是一种 用于管理元素分组 的数据结构。

在一些应用问题中,需将n个不同的元素划分成一些不相交的集合,开始时,n个元素各自成一个集合,然后按照一定规律将部分集合合成一个集合,也就是集合合并并查集(union-find)适合来描述这类问题。

对于并查集,我们可以将它看成是一个森林,森林是由多棵树组成的,并查集中的一个个集合就可以看作是树。

示例:

二,并查集的结构 

并查集的存储结构和树的双亲表示法相似。

所谓双亲表示法,就是在树的节点中,只存储父节点的指针,不存储孩子节点的指针。通过指针可以找到父节点。因为对于一颗树来说,可能有多个孩子 ,但只有一个父节点。

 

对于上图中:

节点0的数组值为-4,说明该节点为根节点。

节点6的数组值为0,说明该节点的父节点为0。

节点7的数组值为0,说明该节点的父节点为0。

节点8的数组值为0,说明该节点的父节点为0。

三,并查集的代码实现 

并查集主要支持一下操作:

  • 查询(find),查询一个元素在哪个集合中。
  • 合并(union),将两个集合合并为一个。

1,并查集的大致结构和初始化

class UnionFind
{
public:
    UnionFind(size_t n)
        :_ufs(n,-1)
    {}

    //......
private:
    vector<int> _ufs;
};

2,find操作 

在并查集中找到包含x的根

int findRoot(int x)
{
    int root = x;

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

    return root;
}
 

3,Union操作

合并两个集合

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]))//root1节点更少
    {
        _ufs[root2] += _ufs[root1];
        _ufs[root1] = root2;   //小树合并到大树
    }
    else
    {
        //root2节点更少
        _ufs[root1] += _ufs[root2];
        _ufs[root2] = root1;
    }
}

4,优化 

当树比较高时,我们在反复查某个节点的根节点时,每次都会花费大量时间。

优化方法路径压缩,只要查找某个节点一次,就将查找路径上的所有节点挂到根节点下面。

如图:查找D的根A,查找路径上包含节点B,将节点D和节点B直接挂在根节点A的下面。

//路径压缩
int findRoot(int x)
{
    int root = x;

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

    //路径压缩
    while (_ufs[x] >= 0)
    {
        int parent = _ufs[x];
        _ufs[x] = root;   //挂在根节点的下面
        x = parent;
    }

    return root;
}

小结:

上述实现的并查集,支持连续元素。如果是处理非连续元素,需要使用哈希表代替数组(需额处理元素与索引的映射)。

核心思路:

  • 哈希映射unordered_map将任意类型元素映射为连续整数ID,内部用数组管理父节点
  • 动态扩容:自动添加新元素,无需预先指定规模。

  • 模板化:支持泛型数据类型(如string等)。

四,并查集的应用场景

  1. 连通性检测:判断网络中两个节点是否连通。

  2. 最小生成树(Kruskal算法):动态合并边,避免环。

  3. 社交网络分组:快速合并好友关系,查询是否属于同一社交圈。

总结:

并查集通过高效的查找与合并操作,成为处理动态连通性问题的核心数据结构。其优化方法(路径压缩、按秩合并)确保了接近常数的单次操作时间复杂度,适用于大规模数据场景。

其中的按秩合并就是合并集合时小树向大树合并。

省份数量[OJ题] 

题目链接:LCR 116. 省份数量 - 力扣(LeetCode)

 isConnected[i][j]=1,表示城市i和j连通,连通的城市为一个省份。用并查集将连通的数据放入一个集合,再统计最后的集合个数即可。

class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        int n=isConnected.size();
        vector<int> _ufs(n,-1);
        
        //查找根
        auto find=[&](int x)->int
        {
            int root=x;
            while(_ufs[root]>=0)
            root=_ufs[root];

            return root;
        };

        for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
        {
            if(isConnected[i][j]==1)
            {
                //合并i和j集合
                int rooti=find(i),rootj=find(j);
                if(rooti!=rootj)
                {
                    _ufs[rooti]+=_ufs[rootj];
                    _ufs[rootj]=rooti;
                }
            }
        }
        //统计集合数
        int ret=0;
        for(auto x:_ufs)
        {
            if(x<0)
            ret++;
        }

        return ret;
    }
};

冗余连接[OJ题]

题目链接:684. 冗余连接 - 力扣(LeetCode)

class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        //遍历edges数组
        //将在同一条边中的两个顶点放入一个集合
        //如果这条边的两个顶点已经在同一个集合中,加入这条边后,会出现环 ,返回这条边
        vector<int> ufs(1010);
        int sz=edges.size();
        //初始化时各元素自成一个集合,自己就是根
        for(int i=0;i<sz;i++)
        ufs[i]=i;
        
        for(int j=0;j<sz;j++)
        {
            //找到边的两个顶点所在的集合,也就是根节点
            int root1=find(edges[j][0],ufs);
            int root2=find(edges[j][1],ufs);
            //如果在一个集合,加入这条边后,会出现环
            if(root1==root2)
            return edges[j];
            else
            {
                //两个集合独立,合并两个集合
                ufs[root1]=root2;
            }
        }
        return {0,0};
    }
    int find(int num,vector<int>& ufs)
    {
        int root=num;
        while(ufs[root]!=root)
        root=ufs[root];
        return root;
    }
};

等式方程的可满足性[OJ题]

本题链接:990. 等式方程的可满足性 - 力扣(LeetCode)

class Solution {
public:
    bool equationsPossible(vector<string>& equations) {
        //并查集
        vector<int> ufs(26,-1);
        auto findroot=[&](int x)
        {
            int parent=x;
            while(ufs[parent]>=0)
            parent=ufs[parent];
            return parent;
        };

        //将相等的放入同一集合中
        for(auto& str:equations)
            if(str[1]=='=')
            {
                int root1=findroot(str[0]-'a');
                int root2=findroot(str[3]-'a');
                 if(root1!=root2)
                 {
                    ufs[root1]+=ufs[root2];
                    ufs[root2]=root1;
                 }
            }
        //遇到!,如果在同一个集合,返回false
        for(auto& str:equations)
        {
            if(str[1]=='!')
            {
                int root1=findroot(str[0]-'a');
                int root2=findroot(str[3]-'a');
                if(root1==root2)
                return false;
            }
        }
        return true;
    }
};

 

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

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

相关文章

Java 集合数据处理技巧:使用 Stream API 实现多种操作

​ 在 Java 开发中&#xff0c;对集合数据进行处理是非常常见的需求&#xff0c;例如去重、排序、分组、求和等。Java 8 引入的 Stream API 为我们提供了一种简洁、高效的方式来处理集合数据。本文将详细介绍如何使用 Stream API 实现多种集合数据处理操作&#xff0c;并给出相…

OSI 参考模型和 TCP/IP 参考模型

数据通信是很复杂的&#xff0c;很难在一个协议中完成所有功能。因此在制定协议时经常采用的思路是将复杂的数据通信功能由若干协议分别完成&#xff0c;然后将这些协议按照一定的方式组织起来。最典型的是采用分层的方式来组织协议&#xff0c;每一层都有一套清晰明确的功能和…

SD NAND 的 SDIO在STM32上的应用详解(上篇)

目录 上篇&#xff1a; 一.SDIO简介 二.SD卡简介/内部结构 1.SD卡/SD NAND引脚 2.SD卡寄存器 3.FLASH存储器 三.SDIO总线拓扑 中篇&#xff1a; 四.SDIO功能框图(重点) 1.SDIO适配器 2.控制单元 3.命令通道(重点) 4.数…

基于图像处理的裂缝检测与特征提取

一、引言 裂缝检测是基础设施监测中至关重要的一项任务,尤其是在土木工程和建筑工程领域。随着自动化技术的发展,传统的人工巡检方法逐渐被基于图像分析的自动化检测系统所取代。通过计算机视觉和图像处理技术,能够高效、精确地提取裂缝的几何特征,如长度、宽度、方向、面…

执行pnpm run dev报错:node:events:491 throw er; // Unhandled ‘error‘ event的解决方案

vite搭建的vue项目&#xff0c;使用pnpm包管理工具&#xff0c;执行pnpm run dev&#xff0c;报如下错误&#xff1a; 报错原因&#xff1a; pnpm依赖安装不完整&#xff0c;缺少esbuild.exe文件&#xff0c;导致无法执行启动命令。 解决方案&#xff1a; 根据错误提示中提到…

「软件设计模式」建造者模式(Builder)

深入解析建造者模式&#xff1a;用C打造灵活对象构建流水线 引言&#xff1a;当对象构建遇上排列组合 在开发复杂业务系统时&#xff0c;你是否经常面对这样的类&#xff1a;它有20个成员变量&#xff0c;其中5个是必填项&#xff0c;15个是可选项。当用户需要创建豪华套餐A&…

uniapp 安卓10+ 选择并上传文件

plus.io.chooseFile({title: 选择文件,filetypes: [mp3], // 允许的文件类型multiple: false, // 是否允许多选}, (res) > {console.log(虚拟路径666&#xff1a;, res);var arr[{name: files,uri: res.files[0],}]let obj {"tableName": "mingmen_daily_mi…

【第1章:深度学习概览——1.6 深度学习框架简介与选择建议】

嘿,各位老铁们,今天咱们来一场深度学习框架的深度探索之旅。在这个充满无限可能的深度学习时代,深度学习框架就像是连接理论与实践的桥梁,帮助我们从算法设计走向实际应用。随着技术的飞速发展,深度学习框架的选择变得越来越多样化,每一种框架都有其独特的优势和适用场景…

网页制作02-html,css,javascript初认识のhtml的文字与段落标记

用一首李白的将进酒,对文字与段落标记进行一个简单的介绍演示&#xff1a; 目录 一、标题字 1、标题字标记h 2、标题字对其属性align 二、文本基本标记 1、字体属性face 2、字号属性size 3、颜色属性 Color 三、文本格式化标记 1、粗体标记 b &#xff0c;strong 2、…

一.数据治理理论架构

1、数据治理核心思想&#xff1a; 数据治理理论架构图描绘了一个由顶层设计、管控机制、核心领域和管理系统四个主要部分组成的数据治理框架。它旨在通过系统化的方法&#xff0c;解决数据治理机制缺失引发的业务和技术问题&#xff0c;并最终提升企业的数据管理水平。 数据治…

PHP基础部分

但凡是和输入、写入相关的一定要预防别人植入恶意代码! HTML部分 语句格式 <br> <hr> 分割符 <p>插入一行 按住shift 输入! 然后按回车可快速输入html代码(VsCode需要先安装live server插件) html:<h1>标题 数字越大越往后</h1> <p…

人工智能 - 主动视觉可能就是你所需要的:在双臂机器人操作中探索主动视觉

AV-ALOHA 系统使用用于 AV 的 VR 耳机实现直观的数据收集&#xff0c;并且 用于作的 VR 控制器或引线臂。这有助于捕捉全身和头部 远程作我们的真实和模拟系统的运动&#xff0c;记录来自 6 个的视频 不同的摄像头&#xff0c;并为我们的 AV 仿制学习策略提供训练数据。 加州大…

DeepSeek 助力 Vue 开发:打造丝滑的日期选择器(Date Picker),未使用第三方插件

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

Kafka偏移量管理全攻略:从基础概念到高级操作实战

#作者&#xff1a;猎人 文章目录 前言&#xff1a;概念剖析kafka的两种位移消费位移消息的位移位移的提交自动提交手动提交 1、使用--to-earliest重置消费组消费指定topic进度2、使用--to-offset重置消费offset3、使用--to-datetime策略指定时间重置offset4、使用--to-current…

一周学会Flask3 Python Web开发-Debug模式开启

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 默认情况&#xff0c;项目开发是普通模式&#xff0c;也就是你修改了代码&#xff0c;必须重启项目&#xff0c;新代码才生效&…

java练习(28)

ps&#xff1a;练习来自力扣 给定一个二叉树&#xff0c;判断它是否是平衡二叉树 // 定义二叉树节点类 class TreeNode {int val;TreeNode left;TreeNode right;TreeNode() {}TreeNode(int val) { this.val val; }TreeNode(int val, TreeNode left, TreeNode right) {this.va…

Web安全|渗透测试|网络安全

基础入门(P1-P5) p1概念名词 1.1域名 什么是域名&#xff1f; 域名&#xff1a;是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称&#xff0c;用于在数据传输时对计算机的定位标识&#xff08;有时也指地理位置&#xff09;。 什么是二级域名多级域名&am…

OpenHarmony 系统性能优化——默认关闭全局动画

笔者最近发现&#xff0c;关闭OpenHarmony全局动画&#xff0c;系统UI的响应速度会极大的提升 1.全局动画的开关由系统属性persist.sys.arkui.animationscale来控制&#xff0c;默认为1。也就是 动画缩放 1x 2.如果让persist.sys.arkui.animationscale默认为0,也就是关闭的状态…

C 程序多线程拆分文件

C 程序多线程拆分文件 在C语言中&#xff0c;实现多线程来拆分文件通常需要借助多线程库&#xff0c;比如 POSIX 线程库&#xff08;pthread&#xff09;或者 Windows 的线程库&#xff08;CreateThread 或类似的函数&#xff09;。下面我将分别展示在 Linux 和 Windows 环境下…

【Linux】Ubuntu Linux 系统——Python集成开发环境

ℹ️大家好&#xff0c;我是练小杰&#xff0c;今天周四了&#xff0c;明天就周五了&#xff0c;再坚持坚持又能休息了&#xff01;&#xff01;&#x1f606; 本文是有关Linux 操作系统中Python集成开发环境基础知识&#xff0c;后续将添加更多相关知识噢&#xff0c;谢谢各位…