C++【并查集】

news2024/11/16 20:45:46

文章目录

  • 一、并查集是什么
    • 并查集的简单表示
    • 并查集的合并
    • 并查集的代码实现
    • 并查集小练习1
    • 并查集小练习2
    • 并查集的压缩问题

一、并查集是什么

并查集是一个森林
在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-findset)。

0123456789
-1-1-1-1-1-1-1-1-1-1

比如:某公司今年校招全国总共招生10人,西安招4人,成都招3人,武汉招3人,10个人来自不同的学校,起先互不相识,每个学生都是一个独立的小团体,现给这些学生进行编号:{0, 1, 2, 3,4, 5, 6, 7, 8, 9}; 给以下数组用来存储该小集体,数组中的数字代表:该小集体中具有成员的个数。

并查集的树的表示

所以说并查集就是在一堆的数据当中分出不同的集合。
当两个集合有交集的时候,这两个集合可能会被合并
所以称为并查集

如何让建立编号和人,人和编号的映射关系

#include <vector>
#include <map>
template<class T>
class UnionFindSet
{
public:
    UnionFindSet(const T*a,size_t n){
        for(size_t i=0;i<n;++i)
        {
            _a.push_back(a[i]);
            _indexMap[a[i]]=i;
        }
    }
private:
    vector<T> _a;//通过编号找人
    map<T,int> _indexMap;//通过人找编号
};

那么我们又如何表示人与人之间的关系呢?

1、像堆类似,用数组表示多棵树,用下表表示关系
2、双亲表示法

并查集的简单表示

最开始我们这里的每一个标号下面存的都是-1,表示每一个个体都是一个集合,这就是我们的最初始状态,表示 十颗树,十个集合。

0123456789
-1-1-1-1-1-1-1-1-1-1

然后根据我们上面的图,我们知道我们的0,6,7,8号组成了我们的第一个小团体。
在这里插入图片描述

然后我们选择我们的0号作为我们第一个小团体的根节点,我们就将我们的1号存储的内容加上我们6号存储的内容。
然后我们将6号存储的内容变成1的索引,也就是变成我们下面的样子

0123456789
-2-1-1-1-1-10-1-1-1

然后0再和7合并,变成我们下面的样子

0123456789
-3-1-1-1-1-100-1-1

然后0再和8合并,变成我们下面的样子

0123456789
-4-1-1-1-1-1000-1

特点

1.一个位置的值是负数,那它就是树的根,这个负数的绝对值就是这棵树的数据个数
2.如果一个位置的值是正数,那他存储的就是双亲的下标。

并查集的合并

所以按照我们上面的算法存储完毕之后,我们的并查集应该是如下的形状

0123456789
-4-3-32120001

在公司工作一段时间后,西安小分队中8号同学与成都小分队1号同学奇迹般的走到了一起,两个小圈子的学生相互介绍,最后成为了一个小圈子:
在这里插入图片描述

这时,我们就需要将1和8合并起来,但是8并不是父节点,所以我们查找8的父节点,我们发现是0号节点,所以我们将将1 中存储大的内容变成0,也就是其父节点是0号节点,然后我们的0号节点减去原本1号节点UN出的-3,也就是变成了-7

0123456789
-70-32120001

并查集的代码实现

#include <vector>
#include <map>
template<class T>
class UnionFindSet
{
public:
    UnionFindSet(size_t n)
        :_ufs(n,-1)
    {}

    void Unnion(int x1,int x2)
    {
        int root1= FindRoot(x1);
        int root2= FindRoot(x2);
        //如果本身`就是在一个集合当中,就没有必要合并了
        if(root1==root2)
            return;
        if(root1>root2)
            //将并查集中更大的那一个变成root1
            swap(root1,root2);
        //将我们的root2的集合合并到我们的root当中
        _ufs[root1]+=_ufs[root2];
        _ufs[root2]=root1;
    }
    int FindRoot(int x)
    {
        int parent=x;
        //如果不是负数,我就不是根
        while(_ufs[parent]>=0)
        {
            //不断查找父亲结点
            parent=_ufs[parent];
        }
        return parent;
    }
    //在不在同一个集合当中
    bool InSet(int x1,int x2)
    {
        //相等就是在一个集合,不相等就不再同一个集合
        return FindRoot(x1)== FindRoot(、x2);
    }
    size_t SetSize()
    {
        size_t size=0;
        for(size_t i=0;i<_ufs.size();++i)
        {
            if(_ufs[i]<0)
            {
                ++size;
            }
        }
        return size;
    }
private:
    vector<T> _ufs;//通过编号找人
};

并查集小练习1

LeetCode省份数量

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

示例 1:
在这里插入图片描述

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
示例 2:
在这里插入图片描述

输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

提示:

1 <= n <= 200
n == isConnected.length
n == isConnected[i].length
isConnected[i][j] 为 1 或 0
isConnected[i][i] == 1
isConnected[i][j] == isConnected[j][i]

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/bLyHh0
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

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

    void Unnion(int x1,int x2)
    {
        int root1= FindRoot(x1);
        int root2= FindRoot(x2);
        //如果本身`就是在一个集合当中,就没有必要合并了
        if(root1==root2)
            return;
        if(root1>root2)
            //将并查集中更大的那一个变成root1
            swap(root1,root2);
        //将我们的root2的集合合并到我们的root当中
        _ufs[root1]+=_ufs[root2];
        _ufs[root2]=root1;
    }
    int FindRoot(int x)
    {
        int parent=x;
        //如果不是负数,我就不是根
        while(_ufs[parent]>=0)
        {
            //不断查找父亲结点
            parent=_ufs[parent];
        }
        return parent;
    }
    //在不在同一个集合当中
    bool InSet(int x1,int x2)
    {
        //相等就是在一个集合,不相等就不再同一个集合
        return FindRoot(x1)== FindRoot(x2);
    }
    size_t SetSize()
    {
        size_t size=0;
        for(size_t i=0;i<_ufs.size();++i)
        {
            if(_ufs[i]<0)
            {
                ++size;
            }
        }
        return size;
    }
private:
    vector<int> _ufs;//通过编号找人
};

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

        for(size_t i=0;i<isConnected.size();++i)
        {
            for(size_t j=0;j<isConnected[i].size();++j)
            {
                //如果i和j是相连的话
                if(isConnected[i][j]==1)
                {
                    ufs.Unnion(i,j);
                }
            }
        }
        return ufs.SetSize();
    }
};

在这里插入图片描述

或者采用我们下面的更加简洁的版本

class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        vector<int> ufs(isConnected.size(),-1);

        auto findRoot=[&ufs](int x){
            while(ufs[x]>=0)
            {
                x=ufs[x];
            }
            return x;
        };
        for(size_t i=0;i<isConnected.size();++i)
        {
            for(size_t j=0;j<isConnected[i].size();++j)
            {
                //如果i和j是相连的话
                if(isConnected[i][j]==1)
                {
                    //合并集合
                    int root1=findRoot(i);
                    int root2=findRoot(j);
                    if(root1!=root2)
                    {
                        ufs[root1]+=ufs[root2];
                        ufs[root2]=root1;
                    }
                }
            }
        }
        int n=0;
        for(auto e:ufs)
        {
            if(e<0)
            {
                ++n;
            }
        }
        return n;
    }
};

并查集小练习2

LeetCode等式方程的可满足性

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:“a==b” 或 “a!=b”。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。

只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。

示例 1:

输入:[“a==b”,“b!=a”]
输出:false
解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。
示例 2:

输入:[“ba","ab”]
输出:true
解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。
示例 3:

输入:[“ab","bc”,“a==c”]
输出:true
示例 4:

输入:[“ab",“b!=c”,"ca”]
输出:false
示例 5:

输入:[“cc","bd”,“x!=z”]
输出:true

提示:

1 <= equations.length <= 500
equations[i].length == 4
equations[i][0] 和 equations[i][3] 是小写字母
equations[i][1] 要么是 ‘=’,要么是 ‘!’
equations[i][2] 是 ‘=’

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/satisfiability-of-equality-equations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class Solution {
public:
    bool equationsPossible(vector<string>& equations) {
        vector<int> ufs(26,-1);

        auto findRoot=[&ufs](int x){
            while(ufs[x]>=0)
            {
                x=ufs[x];
            }
            return x;
        };
        //第一遍,先把相等的值加到一个集合中
        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;
    }
};

在这里插入图片描述

并查集的压缩问题

当我们在处理数据量非常大的情况的时候,我们的可能需要压缩我们的路径
比方说我们下面的这种情况
在这里插入图片描述
我们最好将我们的2的父节点更新成0,从而优化我们的路径。
什么时候优化我们的路径?
在查找我们的根结点的时候。
当你查找根节点的时候,发现查找的并不是一层根节点,我们就需要更新我们的根节点。
比方说我们上面2的父亲是1,1并不是根节点,1的父亲是0,我们就直接将我们2的路径变成0,这样之后再查找的时候,查找的速度就会变快。

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

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

相关文章

CS61A Lab 4

更好的阅读体验 Lab 4: Recursion, Tree Recursion lab04.zip What Would Python Do? Q1: Squared Virahanka Fibonacci Use Ok to test your knowledge with the following “What Would Python Display?” questions: python3 ok -q squared-virfib-wwpd -u✂️Hint: If…

CountDownLatch类的使用

&#x1f388;专栏链接:多线程相关知识详解 目录 一.CountDownLatch的介绍 二.CountDownLatch类里面的方法 三.CountDownLatch的两种应用场景 ①一等多情况 ②多等一情况 一.CountDownLatch的介绍 CountDownLatch是一种用来控制多线程的工具类,它被称为门阀、计数器或者…

LeetCode HOT 100 —— 301.删除无效的括号

题目 给你一个由若干括号和字母组成的字符串 s &#xff0c;删除最小数量的无效括号&#xff0c;使得输入的字符串有效。 返回所有可能的结果。答案可以按 任意顺序 返回。 思路 DFS 回溯算法&#xff1a; 首先最终合法的方案&#xff0c;必然有左括号的数量 右括号的数量 …

钉钉获取免登用户信息

大家好&#xff0c;这里是一口八宝周&#x1f44f; 欢迎来到我的博客❤️一起交流学习 文章中有需要改进的地方请大佬们多多指点 谢谢&#x1f64f; 最近好像搞了个什么钉钉小程序&#xff0c;具体做什么咱也不知道&#xff0c;就让我搞一个钉钉获取免登录用户信息的接口出来&…

计网理论模拟

一. 单选题&#xff08;共10 题&#xff0c;20.0分&#xff09; 1. (单选题,2.0分)网络协议主要由 3 个基本要素组成&#xff0c;即&#xff08; &#xff09; A. 层次、语义和同步B. 语法、原语和同步C. 语法、语义和同步D. 语法、语义和功能 正确答案: C 2. (单选题,2.0分…

计算机毕业设计ssm+vue基本微信小程序的智能图书管理系统

项目介绍 本设计旨在研究一种社区图书管理系统设计与实现系统,以各种浏览器web页面加上云服务器后端服务系统,通过这一设计过程,进一步熟悉web前端开发技术和云服务器后端开发技术和方法,培养理论联系实际及知识的综合运用能力。 图书管理系统可以有效实现图书管理的规范化、系…

SAP Gateway 后台模型的缓存设置

/iwbep/cl_mgw_med_provider 类里的成员 mv_cache_active: 这个 cache 默认是开启状态。 调用 OData 服务的 MPC_EXT 类的 get_last_modified 方法获取最后一次修改的时间戳。这个时间戳(timestamp)也会影响到 cache 的行为&#xff0c;我们后续也会详细讨论。 第12 行 super 方…

PySpark--spark local 的环境部署

Spark环境搭建-Local 环境搭建 基本原理 本质&#xff1a;启动一个JVM Process进程(一个进程里面有多个线程)&#xff0c;执行任务Task Local模式可以限制模拟Spark集群环境的线程数量, 即Local[N] 或 Local[*]其中N代表可以使用N个线程&#xff0c;每个线程拥有一个cpu core。…

【使用Netty实现群发消息】

使用Netty实现群发消息netty简单介绍实现群发流程图代码实现NettyServer 类MyChannelInitializer 类MyServerHandler 类ChannelHandler 类Netty 依赖效果展示netAssist 工具启动Netty server打开netAssist 工具netty简单介绍 Netty是由JBOSS提供的一个java开源框架&#xff0c…

第三十一章 linux-模块的加载过程

第三十一章 linux-模块的加载过程 文章目录第三十一章 linux-模块的加载过程sys_init_modulestruct moduleload_module在用户空间&#xff0c;用insmod这样的命令来向内核空间安装一个内核模块&#xff0c;本节将详细讨论模块加载时的内核行为。当调用“insmod demodev.ko”来安…

通讯录的思路与实现(C语言)

目录 前言 程序的分装 程序的结构 函数实现 通讯录的初始化 通讯录的扩容 将数据保存到本地 增加联系人 显示通讯录所有联系人 目标联系人的检索(根据名称) 目标联系人的检索(根据号码) 检索发展来的函数 删除联系人 查询目标联系人 联系人信息的更改 按名称对通…

Python写个“点球大战”小游戏

大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 看过我Python入门教程的朋友应该会看到其中有提到一个点球小游戏的作业。 在世界杯决赛即将到来之际&#xff0c;我们再来回顾一下这个小游戏。对于刚刚学习编程不久的同学&#xff0c;这是个不错的练手习题&…

(二)RT-Thread入门——线程管理

目录 线程管理 线程管理特点 线程工作机制 线程控制块 线程属性 线程栈 线程状态 线程优先级 时间片 线程入口函数 无限循环模式 顺序执行或有限次循环模式 线程错误码 线程状态切换 线程操作 创建动态线程 删除 初始化静态线程 脱离 获得当前线程 让出…

数据结构基础篇》》用c语言实现复数的八个基本运算

数据结构开讲啦&#xff01;&#xff01;&#xff01;&#x1f388;&#x1f388;&#x1f388; 本专栏包括&#xff1a; 抽象数据类型线性表及其应用栈和队列及其应用串及其应用数组和广义表树、图及其应用存储管理、查找和排序将从简单的抽象数据类型出发&#xff0c;深入浅出…

B-013 缓启动电路设计

缓启动电路设计1 简介2 案例分析2.1 电路说明2.2 原理分析3 电路参数设定说明1 简介 缓启电路的供电是由一个PMOS控制通断的&#xff0c;软启动的设计是让PMOS的导通时间变缓&#xff0c;电路上的做法是在PMOS的栅极和源极之间接一个合适的电容&#xff0c;PMOS的导通时间就会…

Arcgis中创建Python脚本工具

文章目录创建工具步骤第一步&#xff1a;第二步&#xff1a;第三步&#xff1a;定义工具工具箱Toolbox工具类1、__init__2、getParameterInfo3、isLicensed4、updateParameters5、updateMessage6、execute进度条的使用代码相比于自定义工具箱的源脚本和参数定义难以集中管理的缺…

中国专利电子申请网站系统环境配置方法

一、在线平台使用环境要求 支持的操作系统、浏览器、office的版本如下&#xff0c;必须匹配对应的版本&#xff1a; 操作系统&#xff1a;WINDOWS XP、WINDOWS 7、WINDOWS 8 浏览器&#xff1a;IE8、IE9、IE10 文档编辑软件&#xff1a;OFFICE2003、OFFICE2007 强烈推荐使用中…

1. Maven基础

1. Maven简介 Maven是专门用于管理和构建Java项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构 提供了一套标准化的构建流程&#xff08;编译&#xff0c;测试&#xff0c;打包&#xff0c;发布……&#xff09; 提供了一套依赖管理机制 1.1…

Allegro快速编辑丝印文字操作指导

Allegro快速编辑丝印文字操作指导 Allegro支持丝印文字的编辑,下面介绍快速编辑丝印文字的两种方法如下 以编辑下方丝印文字为例 方法一: 选择Text edit 命令 点击丝印文字,丝印会被高亮起来 输入需要更改后的文字,如下 右击选择done 文字被更改好了 方法二 选择se…