数据结构---并查集

news2024/11/13 10:13:07

目录标题

  • 为什么会有并查集
  • 并查集的原理
  • 模拟实现并查集
    • 准备工作
    • 构造函数
    • FindRoot
    • Union
    • SetCount
  • 并查集实战
    • 题目一:省份数量
    • 题目解析
    • 题目二:等式方程的可满足性
    • 题目解析

为什么会有并查集

这里可以使用生活中的一个例子来带着大家理解并查集,大家在上学的过程中肯定会发现一种现象,在开学之前大家谁也不认识谁每个人都是一个小团体,可是开学之后因为座位旁边有一个同桌,所以开学没多久你和同桌就会互相认识并且开心的玩在一起,那么这时就是两个一个人的小团体融合成为了一个两个人的小团体,后来你可能会经常把头朝向后面看从而认识了你后面的人,经过了解之后你又跟你后桌的人相互认识从而带着你的同桌和他们玩在一起,那么这个时候两个两人的小团体就会融合成为一个4人的小团体,随着时间的流逝,不同人数的团体相互融合,最终一个班级的人从每个人都是一个小团体变成了一个所有人在一起成为一个大团体,那么为了描述不同团体进行融合的过程就有了查并集这个数据结构。

并查集的原理

我们先来看看并查集的比较官方的定义:在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-findset)。并查集是一个森林,所谓的森林就是指由多个树组成,比如说某公司今年校招全国总共招生10人,西安招4人,成都招3人,武汉招3人,10个人来自不同的学校,起先互不相识,每个学生都是一个独立的小团体,现给这些学生进行编号:{0, 1, 2, 3,4, 5, 6, 7, 8, 9}; 给以下数组用来存储该小集体,数组中数字的绝对值代表:该小集体中具有成员的个数,那么这个数组就如下:
在这里插入图片描述
毕业后学生们要去公司上班,每个地方的学生自发组织成小分队一起上路,那么这个时候他们由一个人一个团体合并成为多个人一个团体,于是西安学生小分队s1={0,6,7,8},成都学生小分队s2={1,4,9},武汉学生小分队s3={2,3,5},10个人形成了三个小团体。假设右三个0,1,2担任队长,那么当前的结构就应该变成下面这样:
在这里插入图片描述
三个团体就是三个树,这三个树再构成一个森林,那么这里就有个问题我们如何把这三个树组合成为一个森林呢?答案是使用数组来对这些树进行合并,数组的下标对应着元素,下标对应的值就表示着不同元素之间的关系,如果你是根节点那么你下标对应的值就表示你这个树里面的节点个数,如果你是子树根节点或者子节点的话,那么你下标对应的值就是你父节点在数组中所在的位置,比如说元素0是西安这个树的根节点,元素0对应在数组中的下标为0,那么下标0中记录的数据就为-4表示西安这个树里面有4个节点,再比如说元素5对应的下标是5,那么在数组中下标5里面记录的数据就是2,表示当前的节点为子节点该节点的父节点在数组中的位置为5,那么其他节点也是依次类推,所以上面的数组就会变成下面这个样子:
在这里插入图片描述
知道了如何表示多棵树之后我们就来看看如何将两个树合并成为一棵树,比如说将上面的西安和成都进行合并那么我们就可以通过修改成都树中根节点的父节点,让其指向西安树的根节点来实现,那么这里的图片就变成下面这样:
在这里插入图片描述
然后在数组中我们就得修改两个树的根节点所对应下标的值,首先将下标为1的值修改成为0表示节点1的父节点为0,将下标为0的值修改成为-7因为融合了一个新的树所以当前树中节点的个数就变多了,那么当前吧数组中的内容就变成下面这样,红色背景表示不同树的根节点,红色从原来的3个变成了2个,那么这就说名当前的森林只有两个树
在这里插入图片描述
知道了森林的表示方法和树融合的原理之后我们就可以来模拟实现并查集。

模拟实现并查集

准备工作

首先类里面得存在一个整型数组用来表示每个元素之间的关系:

class UnionFindSet
{
public:

private:
	vector<int> _ufs;//用来表示元素之间的关系
};

但是这样做就会存在一个问题:我们怎么知道数组中的下标对应的是哪个元素呢?所以我们还得创建一个vector容器来方便我们查找下标所对应的元素,又因为元素可以是各种类型所以这里我们得添加一个类模板,模板中存在一个参数表示当前并查集处理的是哪种类型数据的关系,有了这个容器之后我们可以查看下标所对应的元素,那如果我们想查看元素对应的下标又该如何解决呢?所以我们还得创建一个map容器来记录每个元素所对应的下标,那么当前的代码就成为了下面这样:

template<class T>
class UnionFindSet
{
public:

private:
	vector<int> _ufs;//用来表示元素之间的关系
	vector<T> _a;//根据下标找元素
	map<pair<T, int>> _indexmap;//根据元素找到下标
};

构造函数

构造函数需要两个参数,一个参数接收当前容器需要处理的数据数组,另外一个参数表示当前处理的数据个数:

UnionFindSet(const T* sorce, size_t num)
{

}

然后我们就创建一个循环,在循环里面分别取参数数组的值将其插入到数组_a里面,因为循环是从0开始并且数组_a也是从下标为0的位置开始插入,所以在循环里面我们可以顺便往_indexmap容器种插入数据,那么这里的代码就如下:

UnionFindSet(const T* sorce, size_t num)
{
	for (size_t i = 0; i < num; i++)
	{
		_a.push_back(sorce[i]);
		_indexmap[sorce[i]] = i;
	}
}

最后将容器_ufs的长度扩容到num,并将每个元素的值都初始化为-1,那么完整的代码就如下:

UnionFindSet(const T* sorce, size_t num)
	:_ufs(num,-1)
{
	for (size_t i = 0; i < num; i++)
	{
		_a.push_back(sorce[i]);
		_indexmap[sorce[i]] = i;
	}
}

我们可以使用下面的代码来进行以下测试:

int main()
{
	string s1[] = { "张三","李四","王五","赵六" };
	UnionFindSet<string> uf(s1, 4);
	return 0;
}

通过调试我们便可以看到这个容器里面的内容如下:
在这里插入图片描述
因为我们没有做出任何的合并操作所以ufs数组里面每个元素的值都是-1,_a数组里面记录的是下标所对应的元素,0对应的是张三,1对应的是李四,2对应的是王五,3对应的是赵六,然后_indexmap里面就记录的是元素对应的下标,经过仔细的对比可以看到里面记录的内容和数组中的内容相对应,那么我们的构造函数就实现完成了,接下来来看查找函数。

FindRoot

FindRoot函数就查找一个元素的根节点,如果一个节点不为根节点那么在数组里面它存储的就是它的父节点的下标,如果一个节点为根节点那么它存储的就是当前树种含有节点的个数的赋值,所以在函数里面我们可以创建一个while循环在循环里面一直提取数组中记录的下标,直到下标对应的值为负数位置,那么这里的代码就如下:

size_t FindRoot(T tmp)
{
	int x = _indexmap.find(tmp)->second;
	while (_ufs[x] >= 0)
	{
		x = _ufs[x];
	}
	return x;
}

Union

传递两个元素给Union函数,那么该函数就能将两个元素所在的树进行合并,在函数的开始我们先判断一下这两个元素所在树的根节点是否相同,如果相同如果不相同的话我们就进行合并,那么这里的代码就如下:

void Union(T tmp1, T tmp2)
{
	size_t x1 = FindRoot(tmp1);
	size_t x2 = FindRoot(tmp2);
	if (x1 != x2)
	{
	//根节点不相等才进行合并
	}
}

合并的过程很简单将某个根节点的值加到另外一个根节点上,然后将值更改成为另外一个根节点的下标即可,那么这里的代码就如下:

void Union(T tmp1, T tmp2)
{
	size_t x1 = FindRoot(tmp1);
	size_t x2 = FindRoot(tmp2);
	if (x1 != x2)
	{
	//根节点不相等才进行合并
		_ufs[x1] += _ufs[x2];
		_ufs[x2] = x1;
	}
}

SetCount

这个函数的功能就是统计当前容器里面存在几棵树,那么这里我们直接通过循环遍历数组_ufs,里面存在几个元素为负数的节点就说明当前容器里面存在几棵树,那么这里的代码就如下:

size_t SetCount()
{
	size_t num = 0;
	for (auto ch : _ufs)
	{
		if (ch < 0)
		{
			num++;
		}
	}
	return num;
}

并查集实战

题目一:省份数量

题目详细:
在这里插入图片描述
题目链接->点击此处尝试做题

题目解析

有了并查集之后做这种题简直就是小菜一碟,首先题目给了我们一个二维数组,这个数组里面表示各个城市之间的相连接情况,如果isConnected[0][1]等于1,那么这就表示1号城市和2号城市相联通,然后把一群相互联接的城市称为省份,最后题目要求我们根据一个二维数组来判断当前存在多少个省份,那么这里我们就可以先创建一个并查集对象,然后创建一个内嵌for循环判断二维数组中相互链接的城市,如果一个第i号城市和第j号城市相互连接的话就使用并查集对这两个城市进行合并,遍历完成之后就可以返回并查集中的SetCount函数来结束本题,因为题目传递的参数是一个vector<vector<int>>的容器,而我们上面实现的并查集的构造函数需要一个数组,所以为了方便我们对上面的类进行简化,让其专门服务于int类型的数据那么这里的代码如下:

class UnionFindSet
{
public:
	UnionFindSet(int size)
		: _set(size, -1)
	{}

	size_t FindRoot(int x)
	{
		while(_set[x] >= 0)
			x = _set[x];

		return x;
	}

	void Union(int x1, int x2)
	{
		int root1 = FindRoot(x1);
		int root2 = FindRoot(x2);

		if(root1 != root2)
		{
			_set[root1] += _set[root2];
			_set[root2] = root1;
		}
	}

	size_t SetCount()
	{
		size_t count = 0;
		for(size_t i = 0; i < _set.size(); ++i)
		{
			if(_set[i] < 0)
				count++;
		}

		return count;
	}

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

然后这道题的代码就如下:

int findCircleNum(vector<vector<int>>& isConnected) {
    UnionFindSet uf(isConnected.size());
    for(int i=0;i<isConnected.size();i++)
    {
        for(int j=0;j<isConnected[0].size();j++)
        {
            if(i!=j&&isConnected[i][j]==1)
            {
                uf.Union(i, j);
            }
        }
    }
    return uf.SetCount();
}

测试的结果如下:
在这里插入图片描述
可以看到运行的结果是正确的。

题目二:等式方程的可满足性

在这里插入图片描述
题目链接->点击此处尝试做题

题目解析

这道题很明显也是使用并查集进行解决,数组中提供很多表达式,那么我们首先创建一个for循环将表达式中所用相等的元素都合并到一起,然后再创建一个循环判断每个不相等的表达式看两遍的元素是否属于同一个树,如果是树的话就直接返回false,如果不属于同一个树的话就接着往下进行判断,如果所有元素都判断完并且没有出错的话就返回true,题目给的参数形式如下:

bool equationsPossible(vector<string>& equations) {

}

我们不知道元素个数,所以这里直接将空间拉到最大一共有26个因为字母,那么这里就开26个大小,然后使用相对映射法进行合并字符a对应的是0,字符b对应的是1这样一直往后,那么这里的代码就如下:

bool equationsPossible(vector<string>& equations) {
	UnionFindSet uf(26);
	for(auto ch:equations)
	{
	    if(ch[1]=='=')
	    {
	        uf.Union(ch[0]-'a', ch[3]-'a');
	    }
	}
	for(auto ch:equations)
	{
	    if(ch[1]=='!')
	    {
	        if(uf.FindRoot(ch[0]-'a')==uf.FindRoot(ch[3]-'a'))
	        {
	            return false;
	        }
	    }
	}
	return true;
	}

代码的运行结果如下:
在这里插入图片描述
可以看到运行的结果是正常的。

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

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

相关文章

机器学习03-数据理解(小白快速理解分析Pima Indians数据集)

机器学习数据理解是指对数据集进行详细的分析和探索&#xff0c;以了解数据的结构、特征、分布和质量。数据理解是进行机器学习项目的重要第一步&#xff0c;它有助于我们对数据的基本属性有全面的了解&#xff0c;并为后续的数据预处理、特征工程和模型选择提供指导。 数据理解…

从Arweave开始:4EVERLAND存储签入挑战开始

嗨&#xff0c;4evers&#xff0c; 今天&#xff0c;我们热烈欢迎您参加 Galxe 上的 4EVERLAND “Arweave 入门”活动。这是一项长期的重头活动&#xff0c;所有参与的用户都有机会获得相应的奖励。 Arweave 是一种革命性的去中心化存储协议&#xff0c;为寻求安全可靠的有价…

基于飞桨paddle的极简方案构建手写数字识别模型测试代码

基于飞桨paddle的极简方案构建手写数字识别模型测试代码 原始测试图片为255X252的图片 因为是极简方案采用的是线性回归模型&#xff0c;所以预测结果数字不一致 本次预测的数字是 [[3]] 测试结果&#xff1a; PS E:\project\python> & D:/Python39/python.exe e:/pro…

第五章 数组

定义 数组是一组相同类型元素的集合&#xff0c;但我们需要创建多个相同类型的变量时&#xff0c;只需要创建一个类型的数组&#xff0c;就相当于同时创建很多相同类型的变量。 一维数组 数组如何创建 从定义来入手看一下数组的创建&#xff1a; type_t arr_name[const_n];…

《向量数据库指南》——FAISS和Chroma:两种流行的向量数据库的比较

目录 FAISS Chroma 比较 向量数据库是一种可以存储和检索高维向量数据的数据库,高维向量数据是一种可以表示任何类型数据的A.I原生方式,比如文本、图像、音频等。向量数据库可以用于实现各种基于相似度搜索和聚类的A.I应用,比如语义搜索、推荐系统、图像识别等。在本文中…

Spring Boot——Spring Boot自动配置原理

系列文章目录 Spring Boot启动原理 Spring Boot自动配置原理 系列文章目录前言一、Spring Boot自动配置原理剖析二、自动配置生效三、总结&#xff1a; 前言 一直在使用Spring Boot特别好奇的是为什么Spring Boot比Spring在项目构建和开发过程中要方便很多&#xff0c;无需编…

二叉树的层序遍历(两种方法:迭代+递归)

题目&#xff1a; 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]] 解题思路&#xff1a;迭代法…

【设计模式——学习笔记】23种设计模式——组合模式Composite(原理讲解+应用场景介绍+案例介绍+Java代码实现)

案例引入 学校院系展示 编写程序展示一个学校院系结构: 需求是这样&#xff0c;要在一个页面中展示出学校的院系组成&#xff0c;一个学校有多个学院&#xff0c;一个学院有多个系 【传统方式】 将学院看做是学校的子类&#xff0c;系是学院的子类&#xff0c;小的组织继承大…

位1的个数,编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1‘ 的个数(也被称为汉明重量)。

题记&#xff1a; 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为汉明重量&#xff09;。 提示&#xff1a; 请注意&#xff0c;在某些语言&#xff…

MySQL使用xtrabackup备份和恢复教程

1、xtrabackup说明 xtrabackup是percona开源的mysql物理备份工具。 xtrabackup 8.0支持mysql 8.0版本的备份和恢复。 xtrabackup 2.4支持mysql 5.7及以下版本的备份和恢复。 这里我以xtrabackup 8.0为例讲解备份和恢复的具体操作方法。 xtrabackup 2.4版本的使用上和8.0版本相…

PX4从放弃到精通(二十九):传感器冗余机制

文章目录 前言一、parametersUpdate二、imuPoll三、 put四、 confidence五、 get_best 前言 PX4 1.13.2 一个人可以走的更快&#xff0c;一群人才能走的更远&#xff0c;可加文章底部微信名片 代码的位置如下 PX4冗余机制主要通过传感读数错误计数和传感器的优先级进行选优 …

解决[Vue Router warn]: No match found for location with path “/day“问题

首先是升级vue-router4.0后会警告[Vue Router warn]: No match found for location with path "/day" 找了许久解决方案如下&#xff1a; 一、404页面不需要再异步路由后边添加&#xff0c;直接放到静态路由里即可 二、要注意不能写name&#xff0c;否则会刷新默认…

Parameter ‘roleList‘ not found.

Parameter roleList not found. Available parameters are [arg1, arg0, param1, param2] 多半是Mapper层传入多个参数的时候&#xff0c;没有加Param注解&#xff0c;导致BindException错误

ORA-01187 ORA-01110

ORA-01187: cannot read from file because it failed verification tests ORA-01110: data file 201: ‘/u01/app/oracle/oradata/CNDB/temp01.dbf’ 查询临时文件是存在的 重建临时数据文件 删除临时文件&#xff1a; alter database tempfile /u01/app/oracle/oradata…

56. 合并区间 排序

Problem: 56. 合并区间 文章目录 思路Code 思路 对数组排序&#xff0c;按照左端点从小到大排序。初始化Merged&#xff0c;将第一个区间放入。遍历intervals ,如果当前区间的左端点比merged最后一个区间的右端点大&#xff0c;不重合&#xff0c;直接将该区间加入最后&#xf…

《零基础入门学习Python》第070讲:GUI的终极选择:Tkinter7

上节课我们介绍了Text组件的Indexs 索引和 Marks 标记&#xff0c;它们主要是用于定位&#xff0c;Marks 可以看做是特殊的 Indexs&#xff0c;但是它们又不是完全相同的&#xff0c;比如在默认情况下&#xff0c;你在Marks指定的位置中插入数据&#xff0c;Marks 的位置会自动…

指针的基础应用(数组的颠倒和排序,二维数组的表示)

1.数组的颠倒&#xff1a;若有10个数字&#xff0c;那么数组的颠倒即 a[0]与a[9]交换,a[1]与a[8]交换&#xff0c;a[2]与a[7]交换&#xff0c;......a[4]与a[5]交换&#xff0c;所以到a[4]就颠倒完毕&#xff0c;即 (n-1)/2 若不用指针代码如下 #include<stdio.h>voi…

交互式AI技术与模型部署:使用Gradio完成一项简单的交互式界面

下面的这段代码使用Gradio库创建了一个简单的交互式界面。用户可以输入名称、选择是早上还是晚上、拖动滑动条来选择温度&#xff0c;然后点击"Launch"按钮&#xff0c;界面会显示相应的问候语和摄氏度温度。例如&#xff0c;如果用户输入"John"&#xff0…

iperf3 编译安装及网讯WX1860千兆网口测试

iperf3 编译安装及网讯1860千兆网口测试 编译安装 安装包下载地址:https://github.com/esnet/iperf/archive/refs/tags/3.8.tar.gz 将安装包iperf-3.8.tar.gz拷贝测试系统盘桌面,使用如下命令进行编译安装: tar zxvf iperf-3.8.tar.gz cd iperf-3.8 ./configure make s…

LeetCode-222-完全二叉树的节点个数

一&#xff1a;题目描述&#xff1a; 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节…