C++并查集

news2024/12/26 10:41:06

1.并查集概念

1.1.并查集定义

在一些应用问题中,需要:

  1. n 个不同的元素划分成一些不相交的集合
  2. 开始时,每个元素自成成为一个集合
  3. 然后按一定的规律,将归于同一组元素的集合合并
  4. 期间需要反复用到查询某个元素归属于那个集合的算法

适合于描述这类问题的抽象数据类型称为并查集(union-find set)。

并查集这类问题在实际中有很多体现,例如:一开学的时候,大家都不认识(每个元素自成一个集合),相互熟悉了一段时间,就会产生一些小群体(归并小集合到大集合)。

1.2.并查集术语

  1. 元素(element):指并查集中的一个结点,可以是任何类型的数据结构
  2. 集合(set):由若干个元素组成的子集,所有元素构成并查集的所有集合
  3. 父结点(parent):指一个元素在并查集中的父结点,如果它没有父结点,则为根结点
  4. 根结点(root):一个集合中唯一的没有父结点的元素
  5. 合并(union):将两个集合合并成一个集合的操作,即将一个集合的根结点连接到另一个集合的根结点上
  6. 查找(find):查找一个元素所在的集合的操作,即找到该元素的根结点
  7. 路径压缩(path compression):在查找时,将路径上的每个结点的父结点更新为集合的根结点,其他父结点转为普通结点作为根结点的子结点以加快后续查找的速度
  8. 按秩合并(union by rank):在合并时,通过记录每个集合根节点的秩(即树高),将较矮的树连接到较高的树上,以减小整体树高,提高查找效率

2.并查集接口

#pragma once
#include <vector>

namespace limou
{
	template <typename Type = int>
	class UnionFindSet
	{
		/*
		* 这里的并查集只使用了索引来作为集合内的元素的编号
		* 对应存储的是“根结点包含元素的个数”和“双亲结点索引”的信息
		*/

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

		bool Union(int x1, int x2)
		{
			/* 根据编号合并两个集合:合并之前要先找根,因此可以复用 voidFindRoot() */
		}

		int FindRoot(int x)
		{
			/* 根据编号找编号对应元素的根:通过编号存储的父结点,不断跳转到根即可 */
		}

		bool InSet(int x, int y)
		{
			/* 两编号对应元素是否在同一个集合 */
		}

		size_t Count()
		{
			/* 计算并查集内集合的个数 */
		}

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

3.并查集细节

3.1.映射问题

在探讨并查集问题之前,首先我们需要解决一个问题,我们知道可以使用 key 来寻找 value,那怎么通过 valuekey 呢?我们可以利用 vectormap 结合使用。

#include <vector>
#include <map>
#include <iostream>

namespace limou
{
	template <typename Type>
	class UnionFindSet
	{
	public:
		UnionFindSet(const Type* arr, size_t n)
		{
			for (size_t i = 0; i < n; i++)
			{
				_arr.push_back(arr[i]);
				_indexMap[arr[i]] = i;
			}
		}
	private:
		std::vector<Type> _arr;			//key 找 value
		std::map<Type, int> _indexMap;	//value 找 key
	};
}

int main()
{
	std::string arr[3] = { "limou", "dimou", "iimou" };
	limou::UnionFindSet<std::string> ufs(arr, 3);
	return 0;
}

这样就可以通过 vector 来根据 key 值查找 value,通过 map 来根据 value 查找 key 的双向查找,当然,这么做只是可选的,实际上使用一个vector也可以,就是需要遍历导致性能下降一些(但是为了简化您的代码负担,我下面代码实现中,先使用只有vector的并查集(也就说,我们存储的元素只有 int 数据类型的编号),以后我有时间再来补充拓展…)。

3.2.集合表示

回到并查集,我们应该如何一个小集合呢?使用一颗树来表示,并且采用双亲表示法和索引来构建一个并查集。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中,有一些特征您需要注意一下:

  1. 数组的下标对应集合中元素的编号
  2. 数组中如果为负数,负号代表根,数字代表该集合中元素个数
  3. 数组中如果为非负数,代表该元素双亲在数组中的下标

3.3.集合合并

那么怎么模拟集合之间的合并呢?看看图示即可快速明白:

在这里插入图片描述

也就是将一个集合的根结点作为另外一个集合的成员合并进去,并且更新索引对应的值即可。

3.4.压缩路径

如果数据量比较大,就会产生寻根时间长的问题,因此就会有一种思路认为:反正所有的元素都在一个集合内,那干脆超过一定层数,或者干脆直接重构一个集合(重构一颗树),让元素在一个层内,这样寻根就会方便很多。当然,这里我为了给您呈现更为简单的数据结构,这一实现我就暂时忽略,以后有时间再来给您补充…

4.并查集实现

4.1.具体实现

接下来实现一个简答的并查集:

#pragma once
#include <vector>

namespace limou
{
	template <typename Type = int>
	class UnionFindSet
	{
		/*
		* 这里的并查集只使用了索引来作为集合内的元素的编号
		* 对应存储的是“根结点包含元素的个数”和“双亲结点索引”的信息
		*/

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

		bool Union(int x1, int x2)
		{
			/* 根据编号合并两个集合:合并之前要先找根,因此可以复用 voidFindRoot() */
			int root1 = FindRoot(x1);
			int root2 = FindRoot(x2);

			if (root1 != root2)
			{
				_ufs[root1] += _ufs[root2];//谁合并谁都可以,没有特别强制的规定
				_ufs[root2] = root1;
				return true;
			}

			return false;
		}

		int FindRoot(int x)
		{
			/* 根据编号找编号对应元素的根:通过编号存储的父结点,不断跳转到根即可 */
			int parent = x;
			while (_ufs[x] >= 0)
			{
				parent = _ufs[x];
				x = parent;
			}

			return parent;
		}

		bool InSet(int x, int y)
		{
			/* 两编号对应元素是否在同一个集合 */
			return FindRoot(x) == FindRoot(y);
		}

		size_t Count()
		{
			/* 计算并查集内集合的个数 */
			size_t count = 0;
			for (auto it : _ufs)
			{
				if (it < 0)
					++count;
			}

			return count;
		}

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

4.2.测试用例

#include <iostream>
#include "union_find_set.hpp"
using namespace std;
using namespace limou;
int main()
{
	//{0}, {1}, {2}, {3}
	UnionFindSet<> ufs(4);
	cout << ufs.Count() << '\n';
	if (ufs.InSet(1, 2))
		cout << "OK" << '\n';

	//{0, 1, 2}, {3}
	ufs.Union(0, 1);
	ufs.Union(1, 2);
	cout << ufs.Count() << '\n';
	if (ufs.InSet(1, 2))
		cout << "OK" << '\n';

	//{0, 1, 2} root->0
	//{3} root->3
	cout << ufs.FindRoot(2) << '\n';
	cout << ufs.FindRoot(3) << '\n';

	return 0;
}

5.并查集分析

5.1.并查集优势

  1. 高效的合并与查询操作:并查集通过使用树结构来表示集合,并采用路径压缩和按秩合并等优化技术,使得合并和查询操作的时间复杂度接近于常数级别,具有高效性能
  2. 简单易用:并查集操作简单直观,易于实现和使用。只需要实现合并(Union)和查询(Find)两个基本操作即可,且这些操作的语义清晰明确
  3. 动态增加集合的支持:并查集可以在运行过程中动态地增加或删除集合。当新元素加入时,可以直接创建一个独立的集合;当两个集合合并时,可以通过合并两个根节点来实现
  4. 适用于连通性问题:并查集广泛应用于处理连通性问题,例如判断图中的两个节点是否连通、求解最小生成树、判断图中是否存在环等
  5. 节省存储空间:并查集采用树结构表示集合,相比使用数组或矩阵表示集合,可以节省存储空间。而且通过路径压缩等优化技术,可以进一步减小树的深度,减少存储空间的占用

5.2.并查集劣势

  1. 不支持高效地获取集合中的所有元素:并查集主要用于处理连通性问题,它更注重的是判断两个元素是否属于同一个集合,而不是提供高效的遍历集合中的所有元素的能力。如果需要频繁地遍历集合中的元素,在并查集中可能会有一些局限性
  2. 不支持删除元素:并查集在元素加入后,无法直接删除元素。虽然可以通过一些额外的操作来实现删除元素的功能,但这可能会引入一定的复杂性和开销

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

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

相关文章

python利用requests库进行接口测试的方法详解

前言 之前介绍了接口测试中需要关注得测试点&#xff0c;现在我们来看看如何进行接口测试&#xff0c;现在接口测试工具有很多种&#xff0c;例如&#xff1a;postman,soapui,jemter等等&#xff0c;对于简单接口而言&#xff0c;或者我们只想调试一下&#xff0c;使用工具是非…

迈入数据结构殿堂——时间复杂度和空间复杂度

目录 一&#xff0c;算法效率 1.如何衡量一个算法的好坏&#xff1f; 2.算法效率 二&#xff0c;时间复杂度 1.时间复杂度的概念 2.大O的渐进表示法 3.推导大O的渐进表示法 4.常见时间复杂度举例 三&#xff0c;空间复杂度 一&#xff0c;算法效率 数据结构和算法是密…

【产品】Axure的基本使用(二)

文章目录 一、元件基本介绍1.1 概述1.2 元件操作1.3 热区的使用 二、表单型元件的使用2.1 文本框2.2 文本域2.3 下拉列表2.4 列表框2.5 单选按钮2.6 复选框2.7 菜单与表格元件的使用 三、实例3.1 登录2.2 个人简历 一、元件基本介绍 1.1 概述 在Axure RP中&#xff0c;元件是…

如何使用透明显示屏

透明显示屏的使用主要取决于具体的应用场景和需求。以下是一些常见的使用透明显示屏的方法&#xff1a; 商业展示&#xff1a;透明显示屏可以作为商品展示柜&#xff0c;通过高透明度、高分辨率的屏幕展示商品细节&#xff0c;吸引顾客的注意力。同时&#xff0c;透明显示屏还可…

人工智能基本常识:让深度学习技术更加人性化

近年来&#xff0c;人工智能技术日臻成熟。现在&#xff0c;许多产品和服务都依靠人工智能技术实现自动化和智能化&#xff0c;因此它与我们的日常生活息息相关。无论是为我们带来各种便利的家用设备&#xff0c;还是我们一直在使用的产品制造方式&#xff0c;人工智能的影响无…

【算法题】智能成绩表(js)

总分相同按名字字典顺序。 解法&#xff1a; function solution(lines) {const [personNum, subjectNum] lines[0].split(" ").map((item) > parseInt(item));const subjects lines[1].split(" ");const classMates [];let results [];for (let i…

C++笔记:动态内存管理

文章目录 语言层面的内存划分C语言动态内存管理的缺陷new 和 delete 的使用了解语法new 和 delete 操作内置类型new 和 delete 操作自定义类型 new 和 delete 的细节探究new 和 delete 的底层探究operator new 和 operator new[]operator delete 和 operator delete[] 显式调用…

2023快速上手新红利项目:短剧分销推广CPS

短剧分销推广CPS是一个新红利项目&#xff0c;对于新手小白来说也可以快速上手。 以下是一些建议&#xff0c;帮助新手小白更好地进行短剧分销推广CPS&#xff1a; 学习基础知识&#xff1a;了解短剧的基本概念、制作流程和推广方式。了解短剧的市场需求和受众群体&#xff0c…

wpf devexpress如何使用AccordionControl

添加一个数据模型 AccordionControl可以被束缚到任何实现IEnumerable接口的对象或者它的派生类&#xff08;例如IList,ICollection&#xff09; 如下代码例子示范了一个简单的数据模型使用&#xff1a; using System.Collections.Generic;namespace DxAccordionGettingStart…

zabbix精简模板

一、监控项目介绍 linux自带得监控项目比较多&#xff0c;也不计较杂&#xff0c;很多监控项目用不到。所以这里要做一个比较精简得监控模版 二、监控模板克隆 1.搜索原模板 2.克隆模板 全克隆模板&#xff0c;这样就和原来原模板没有联系了&#xff0c;操作也不会影响原模…

软件测试基础知识+面试总结(超详细整理)

一、什么是软件&#xff1f; 软件是计算机系统中的程序和相关文件或文档的总称。 二、什么是软件测试&#xff1f; 说法一&#xff1a;使用人工或自动的手段来运行或测量软件系统的过程&#xff0c;以检验软件系统是否满足规定的要求&#xff0c;并找出与预期结果之间的差异…

UI设计中的肌理插画是什么样的?

肌理插画本质也和扁平插画差不多&#xff0c;相较扁平插画&#xff0c;肌理插画的层次感、细节更多&#xff0c;也会更立体生动。 肌理插画风格没有描边线&#xff0c;画面轻快&#xff0c;通过色块的明暗来区分每个元素&#xff0c;有点像色彩版的素描&#xff0c;但更简单&a…

一个人全干!之后台管理中的搜索区域的展开收缩组件。

后台管理系统中大多数都有列表的搜索&#xff0c;那么用户的需求又需要必要时收缩搜索区域&#xff0c;需要时再展开。 而且怪的是他还需要一些部分不可收缩&#xff0c;不需要的地方才收缩。使用v-if来解决吧又不咋美观&#xff0c;我们还需要一个简单的动画效果。我们先写一…

橘子学K8S02之容器中所谓的限制

前面我们知道了关于隔离在Linux中的实现是通过一组NameSpace做到的&#xff0c;而且实际上他只是修改了应用进程看到计算机的视图&#xff0c;对他的视野做了限制&#xff0c;只能看到某些特定的内容&#xff0c;但是当你把视角切换到宿主机的操作系统来看的时候&#xff0c;这…

JIT即时编译器深度解析——Java性能提升利器

文章目录 一、JIT概述1、为什么要用JIT即时编译器2、C1、C2与Graal编译器3、分层编译4、热点代码5、热点探测&#xff08;1&#xff09;方法调用计数器&#xff08;2&#xff09;回边计数器 二、编译优化技术1、方法内联&#xff08;1&#xff09;什么是方法内联&#xff08;2&…

银行数据分析指标篇:最全银行数据指标体系打包送给你!

前两天分享了银行业数据分析的案例&#xff0c;今天呢&#xff0c;老李把金融行业的指标体系和典型分析场景完整分享给大家&#xff01;做地通俗易懂&#xff0c;条理清晰&#xff0c;很快就能上手。 银行指标体系 “指标”作为业务和数据的结合&#xff0c;它使得业务目标可…

Vue 2.0源码分析-update

Vue 的 _update 是实例的一个私有方法&#xff0c;它被调用的时机有 2 个&#xff0c;一个是首次渲染&#xff0c;一个是数据更新的时候&#xff1b;由于我们这一章节只分析首次渲染部分&#xff0c;数据更新部分会在之后分析响应式原理的时候涉及。_update 方法的作用是把 VNo…

聚焦本田XR-V和福特领睿:两大SUV综合实力对比,谁更胜一筹?

在当今的SUV市场中&#xff0c;家庭用户的选择变得越来越多样化。特别是对于那些追求时尚、功能性以及技术先进性的用户来说&#xff0c;选择正确的SUV显得尤为重要。本文将重点对比福特领睿和本田XR-V这两款SUV&#xff0c;探讨它们在各方面的表现&#xff0c;做一个综合实力的…

艾江山:你好好养生,我带你去看海

有多少人&#xff0c;还没好好看过这个世界&#xff1b; 有多少人&#xff0c;因为不够健康缺少走出来的勇气。 艾江山第二届平凡人的养生故事大赛暨北海游学&#xff0c;在一片依依不舍中圆满闭幕。 游学博闻&#xff0c;盖谓其因游学所以能博闻也 传统上&#xff0c;游学是…

Power BI - 5分钟学习增加条件列

每天5分钟&#xff0c;今天介绍Power BI增加条件列。 什么是增加条件列&#xff1f; 简单理解&#xff0c;可以根据表中某列设置一个或者多个条件&#xff0c;判定的结果会生成一个新列。 举例&#xff1a; 首先&#xff0c;导入一张【Sales】样例表(Excel数据源导入请参考每…