并查集(高阶数据结构)

news2024/10/7 8:23:50

目录

一、并查集的原理

二、并查集的实现

2.1 并查集的初始化

2.2 查找元素所在的集合

2.3 判断两个元素是否在同一个集合

2.4 合并两个元素所在的集合

2.5 获取并查集中集合的个数

2.6 并查集的路径压缩

2.7 元素的编号问题

三、并查集题目

3.1 省份的数量

3.2 等式方程的可满足性


  • 并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题
  • 并查集通常用森林来表示,森林中的每棵树表示一个集合,树中的结点对应一个元素

说明: 虽然利用其他数据结构也能完成不相交集合的合并及查询,但在数据量极大的情况下,其耗费的时间和空间极大

一、并查集的原理

以朋友圈为例,现在有10个人(从0开始编号),刚开始这10个人互不认识,各自属于一个集合

并查集会用一个数组来表示这10个人之间的关系,数组的下标对应就是这10个人的编号,刚开始时数组中的元素都初始化为-1

说明:数组中某个位置的值为负数,表示该位置是树的根,这个负数的绝对值表示的这棵树(集合)中数据的个数,因为刚开始每个人各自属于一个集合,所以将数组中的位置都初始化为-1

后来这10个人之间通过相互认识,最终形成了三个朋友圈

此时并查集数组中各个位置的值如下

说明:数组中某个位置的值为非负数,表示该位置不是树的根,这个非负数的值就是这个结点的父结点的编号

后来4号和8号又通过某种机遇互相认识了,这时其所在的两个集合就需进行合并,最终就变成了两个朋友圈

在根据两个元素合并两个集合时,需先分别找到这两个元素所在集合的根结点,然后再将一个集合合并到另一个集合,并且合并后需要更新数组中根结点的值

合并集合找根结点的原因:

  1. 若这两个元素所在集合的根结点相同,说明这两个元素本身就在同一个集合,无需合并
  2. 合并集合后需要更新这两个集合的根结点的值

二、并查集的实现

实现并查集时通常会实现如下接口:

  • 初始化并查集
  • 查找元素所在的集合
  • 判断两个元素是否在同一个集合
  • 合并两个元素所在的集合
  • 获取并查集中集合的个数
#include <iostream>
#include <vector>
using namespace std;

class UnionFindSet
{
public:
	// 构造函数
	UnionFindSet(size_t size);
	// 查找元素所在的集合
	int FindRoot(int value);
	// 判断两个元素是否在同一个集合
	bool IsSameSet(int value1, int value2);
	// 合并两个元素所在的集合
	bool Union(int value1, int value2);
	// 获取并查集中集合的个数
	size_t GetSetSize();
private:
	vector<int> _ufs; //维护各个结点间的关系
};

并查集中的数组:

  • 数组的下标依次对应每个元素的编号
  • 数组中元素值为负数,表示下标编号元素为根结点,负数的绝对值表示该集合中元素的个数
  • 数组中元素值为非负数,表示下标编号元素的父结点的编号

2.1 并查集的初始化

并查集中会用一个数组来维护各个结点之间的关系,在初始化并查集时,根据元素的个数开辟数组空间,并将数组中的元素初始化为-1即可

UnionFindSet(size_t size): _ufs(size, -1) {}

2.2 查找元素所在的集合

查找元素所在的集合,本质就是查找元素所在集合的根结点

查找逻辑如下:

  • 若元素对应下标位置存储的是负数,则说明该元素即为根结点,返回该元素即可
  • 若元素对应下标位置存储的是非负数,则跳转到其父结点的位置继续查找根结点

迭代方式实现:

int FindRoot(int value)
{
	int root = value;
	while (_ufs[root] >= 0) 
        root = _ufs[root];
	return root;
}

递归方式实现:

int FindRoot(int x) {
	return _ufs[x] < 0 ? x : FindRoot(_ufs[x]);
}

2.3 判断两个元素是否在同一个集合

要判断两个元素是否在同一个集合,本质就是判断这两个元素所在集合的根结点是否相同

bool IsSameSet(int value1, int value2) {
	return FindRoot(value1) == FindRoot(value2);
}

2.4 合并两个元素所在的集合

合并逻辑如下:

  1. 分别找到两个元素所在集合的根结点
  2. 若这两个元素所在集合的根结点相同,则无需合并。若这两个元素所在集合的根结点不同,则将小集合合并到大集合上
  3. 将小集合根结点的值累加到大集合的根结点上,使得大集合根结点的值的绝对值等于两个集合中元素的总数。
  4. 将小集合根结点的值改为大集合根结点的编号,即将小集合的根结点作为大集合根结点的孩子,使得两个集合变为一个集合
bool Union(int value1, int value2)
{
	int root1 = FindRoot(value1), root2 = FindRoot(value2);
	//本身在同一个集合,不需合并
	if (root1 == root2) return false;
	//合并操作: 数据量小的 往 数据量大的 合并
	if (abs(_ufs[root1]) < abs(_ufs[root2])) swap(root1, root2);
	_ufs[root1] += _ufs[root2];
	_ufs[root2] = root1;
	return true;
}

说明:当两个集合合并时,尽量将小集合合并到大集合上,因为被合并的那个集合中的所有结点在合并后层数都会加一,所以这样做的目的就是为了让较少的结点层数加一,该操作不是必须的

2.5 获取并查集中集合的个数

获取并查集中集合的个数,本质就是统计数组中负值(根结点)的个数

size_t GetSetSize()
{
	size_t count = 0;
	for (int i = 0; i < _ufs.size(); ++i)
		if (_ufs[i] < 0) ++count;
	return count;
}

2.6 并查集的路径压缩

当数据量很大的时候,并查集中树的层数可能会变得很高,这时查找一个元素所在集合的根结点时就需要往上走很多层,此时可以考虑进行路径压缩

路径压缩一般会在查找根结点时进行,当根据一个结点查找其根结点时,该路径上所有的结点都会被压缩,最终这些结点会直接被挂在根结点下,下次再根据这些结点查找根结点时就能快速找到根结点

迭代方式实现:

int FindRoot(int value)
{
	int root = value;
	while (_ufs[root] >= 0) root = _ufs[root];
	//路径压缩
	while (_ufs[value] >= 0)
	{
		int parent = _ufs[value];
		_ufs[value] = root;
		value = parent;
	}
	return root;
}

递归方式实现:

int FindRoot(int x) 
{
	int parent = x; //默认当前结点就是根结点
	if (_ufs[x] >= 0) { //当前结点值不是负数则继续向上找
		parent = FindRoot(_ufs[x]); //找到根结点
		_ufs[x] = parent; //将当前结点的父亲改为根结点(路径压缩)
	}
	return parent;
}

2.7 元素的编号问题

上面在实现并查集时,默认元素的编号都是从0开始依次递增的,但用户所给的编号可能并不是从0开始的,也不是连续的,甚至可能不是数字

可以用模板的方式来实现并查集:

  • 在初始化并查集时,根据所给元素建立元素与数组下标之间的映射关系。
  • 在查找元素所在集合的根结点时,先根据所给元素得到其对应的数组下标,然后再进行查找
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

template<class T>
class UnionFindSet
{
public:
	// 构造函数
	UnionFindSet(const vector<T>& v): _ufs(v.size(), -1) 
	{
		for (int i = 0; i < v.size(); ++i)
			_indexMap[v[i]] = i;
	}
	
	// 查找元素所在的集合
	int FindRoot(const T& value)
	{
		int root = _indexMap[value];
		while (_ufs[root] >= 0) root = _ufs[root];
		//路径压缩
		int tmp = _indexMap[value];
		while (_ufs[tmp] >= 0) {
			int parent = _ufs[tmp];
			_ufs[tmp] = root;
			tmp = parent;
		}
		return root;
	}

	// 判断两个元素是否在同一个集合
	bool IsSameSet(const T& value1, const T& value2) {
		return FindRoot(value1) == FindRoot(value2);
	}

	// 合并两个元素所在的集合
	bool Union(const T& value1, const T& value2)
	{
		int root1 = FindRoot(value1), root2 = FindRoot(value2);
		//本身在同一个集合,不需合并
		if (root1 == root2) return false;
		//合并操作: 数据量小的 往 数据量大的 合并
		if (abs(_ufs[root1]) < abs(_ufs[root2])) swap(root1, root2);
		_ufs[root1] += _ufs[root2];
		_ufs[root2] = root1;
		return true;
	}

	// 获取并查集中集合的个数
	size_t GetSetSize()
	{
		size_t count = 0;
		for (int i = 0; i < _ufs.size(); ++i)
			if (_ufs[i] < 0) ++count;
		return count;
	}
private:
	vector<int> _ufs; //维护各个结点间的关系
	unordered_map<T, int> _indexMap;//维护元素与下标之间的映射关系
};

再使用并查集时就可以传入任意类型的元素了

int main() 
{
	vector<string> v = { "张三", "李四", "王五", "赵六", "田七", "周八", "吴九" };

	UnionFindSet<string> ufs(v);
	cout << ufs.GetSetSize() << endl; //7

	ufs.Union("张三", "李四");
	ufs.Union("王五", "赵六");
	cout << ufs.GetSetSize() << endl; //5

	ufs.Union("张三", "赵六");
	cout << ufs.GetSetSize() << endl; //4

	return 0;
}

三、并查集题目

3.1 省份的数量

LCR 116. 省份数量 - 力扣(LeetCode)

class Solution 
{
public:
    int findCircleNum(vector<vector<int>>& isConnected) 
    {
        vector<int> ufs(isConnected.size(), -1);
        auto findRoot = [&ufs](int value) {
            while(ufs[value] >= 0) value = ufs[value];
            return value;
        };
        auto getSetSize = [&ufs]() {
            size_t count = 0;
            for(int i = 0; i < ufs.size(); ++i)
                if(ufs[i] < 0) ++count;
            return count;
        };
        for(size_t i = 0; i < isConnected.size(); ++i)
            for(size_t j = 0; j < isConnected[0].size(); ++j)
                if(isConnected[i][j] == 1) 
                {
                    int root1 = findRoot(i);
                    int root2 = findRoot(j);
                    if(root1 != root2) 
                    {
                        ufs[root1] += ufs[root2];
                        ufs[root2] = root1;
                    }
                }
        return getSetSize();
    }
};

3.2 等式方程的可满足性

990. 等式方程的可满足性 - 力扣(LeetCode)

class Solution {
public:
    bool equationsPossible(vector<string>& equations) 
    {
        vector<int> ufs(26, -1);
        auto findRoot = [&ufs](int value) {
            while(ufs[value] >= 0) value = ufs[value];
            return value;
        };
        //第一遍,先把相等的值合并到一个集合中
        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/1426303.html

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

相关文章

pytorch调用多个gpu训练,手动分配gpu以及指定gpu训练模型的流程以及示例

torch.device("cuda" if torch.cuda.is_available() else "cpu") 当使用上面的这个命令时&#xff0c;PyTorch 会检查系统是否有可用的 CUDA 支持的 GPU。如果有&#xff0c;它将选择默认的 GPU&#xff08;通常是第一块&#xff0c;即 “cuda:0”&#xf…

python_蓝桥杯刷题记录_笔记_入门3

前言 记录我的解法以及笔记思路&#xff0c;谢谢观看。 题单目录 1.P2141 [NOIP2014 普及组] 珠心算测验 2.P1567 统计天数 3.P1055 [NOIP2008 普及组] ISBN 号码 4.P1200 [USACO1.1] 你的飞碟在这儿 Your Ride Is Here 5.P1308 [NOIP2011 普及组] 统计单词数 6.P1047 […

应急响应事件处置指南

注意&#xff1a;以下的事件处置类型是常见的&#xff0c;但安全威胁不断演化&#xff0c;因此可能需要根据具体情况进行调整。 1 Webshell类 1.1常见Webshell类型 1.1.1 一句话木马 特征&#xff1a; 一句话木马代码简短&#xff0c;通常只有一行代码&#xff0c;使用灵活…

【大厂AI课学习笔记】1.4 算法的进步(1)

2006年以来&#xff0c;以深度学习为代表的机器学习算法的发展&#xff0c;启发了人工智能的发展。 MORE&#xff1a; 自2006年以来&#xff0c;深度学习成为了机器学习领域的一个重要分支&#xff0c;引领了人工智能的飞速发展。作为人工智能专家&#xff0c;我将阐述这一时期…

J-Link:STM32使用J-LINK烧录程序,其他MCU也通用

说明&#xff1a;本文记录使用J-LINK烧录STM32程序的过程。 1. J-LINK驱动、软件下载 1、首先拥有硬件J-Link烧录器。 2、安装J-Link驱动程序SEGGER 下载地址如下 https://www.segger.com 直接下载就可以了。 2.如何使用J-LINK向STM32烧写程序 1、安装好以后打开J-LINK Fl…

废品上门回收小程序搭建全过程

随着人们对环境保护意识的不断增强&#xff0c;废品回收成为了一项重要的社会活动。为了方便废品回收的顾客和回收者之间的联系&#xff0c;废品上门回收小程序成为了一种流行的解决方案。然而&#xff0c;如何选择一款合适的废品上门回收小程序搭建平台呢&#xff1f;下面将为…

网络协议与攻击模拟_13缓存DNS与DNS报文

一、缓存DNS服务器 1、引入缓存DNS 缓存域名服务器需要与外网连接 一台windows作为Client 一台Windows server作为缓存DNS 桥接网络 DHCP自动获取IP地址 Client 192.168.183.133 Windows server 192.168.183.138 ipconfig /all查看下Client的DNS&#xff0c;设置让Cl…

【论文阅读笔记】Advances in 3D Generation: A Survey

Advances in 3D Generation: A Survey 挖个坑&#xff0c;近期填完摘要 time&#xff1a;2024年1月31日 paper&#xff1a;arxiv 机构&#xff1a;腾讯 挖个坑&#xff0c;近期填完 摘要 生成 3D 模型位于计算机图形学的核心&#xff0c;一直是几十年研究的重点。随着高级神经…

深入了解c语言字符串 2

深入了解c语言字符串 2 一 使用 scanf进行字符串的输入&#xff1a;1.1输入单词&#xff08;不包含空格&#xff09;&#xff1a;1.2 输入带空格的整行文本&#xff1a;1.3 处理输入缓冲区&#xff1a;1.4 注意安全性&#xff1a; 二 使用 printf 字符串的输出&#xff1a;三 输…

数据结构之动态查找表

数据结构之动态查找表 1、二叉排序树1.1、二排序树的定义1.2、二叉排序树的查找过程1.3、在二叉排序树中插入结点的操作1.4、在二叉排序树中删除结点的操作 2、平衡二叉树2.1、平衡二叉树上的插入操作2.2、平衡二叉树上的删除操作 3、B_树 数据结构是程序设计的重要基础&#x…

js新增的操作元素类名的方法

Element.classList是一个只读属性&#xff0c;返回一个元素 class 属性的动态 DOMTokenList 集合。这可以用于操作 class 集合。 尽管 classList 属性自身是只读的&#xff0c;但是你可以使用 add()、remove()、replace() 和 toggle() 方法修改其关联的 DOMTokenList。 兼容性…

移动机器人激光SLAM导航(二):运动控制与传感器篇

参考引用 机器人工匠阿杰wpr_simulation 1. 机器人运动控制 1.1 测试环境安装 wpr_simulation 安装$ mkdir -p catkin_ws/src $ cd catkin_ws/src $ git clone https://github.com/6-robot/wpr_simulation.git $ cd wpr_simulation/scripts/ $ ./install_for_melodic.sh # 自…

【2023地理设计组一等奖】基于机器学习的地下水仿真与时空分析

作品介绍 1 设计思想 1.1 作品背景 华北平原是我国最重要的粮棉产地之一,然而近年来农业的低效用水以及过度压采正逐步加剧其地下水资源的紧张性,为经济可持续发展带来重大风险。而地下水动态变化与人为干预、全球气候波动呈现出高度相关性,因此,地下水的仿真模拟对保障粮…

使用阿里云的IDaaS实现知行之桥EDI系统的单点登录

&#xff0c;在开始测试之前&#xff0c;需要确定用哪个信息作为“登陆用户的ID字段”。 这个字段用来在完成SSO登陆之后&#xff0c;用哪个信息将阿里云IDaaS的用户和知行之桥EDI系统的用户做对应。这里我们使用了 phonenumber 这个自定义属性。需要在阿里云做如下配置&#x…

Qt实现类似ToDesk顶层窗口 不规则按钮

先看效果&#xff1a; 在进行多进程开发时&#xff0c;可能会遇到需要进行全局弹窗的需求。 因为平时会使用ToDesk进行远程桌面控制&#xff0c;在电脑被控时&#xff0c;ToDesk会在右下角进行一个顶层窗口的提示&#xff0c;效果如下&#xff1a; 其实要实现顶层窗口&#xf…

openssl3.2 - 官方demo学习 - pkcs12 - pkwrite.c

文章目录 openssl3.2 - 官方demo学习 - pkcs12 - pkwrite.c概述学到的知识点笔记PEM证书可以拼接实验 pkcs12 - pkwrite.c用win10的证书管理器安装P12证书是成功的END openssl3.2 - 官方demo学习 - pkcs12 - pkwrite.c 概述 openssl3.2 - 官方demo学习 - 索引贴 上次PKCS12的…

【Qt】Json在Qt中的使用

Json JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;广泛用于互联网应用程序之间的数据传输。JSON基于JavaScript中的对象语法&#xff0c;但它是独立于语言的&#xff0c;因此在许多编程语言中都有对JSON的解析和生成支持。…

[opencvsharp]C#基于Fast算法实现角点检测

角点检测算法有很多&#xff0c;比如Harris角点检测、Shi-Tomas算法、sift算法、SURF算法、ORB算法、BRIEF算法、Fast算法等&#xff0c;今天我们使用C#的opencvsharp库实现Fast角点检测 【算法介绍】 fast算法 Fast(全称Features from accelerated segment test)是一种用于角…

集合问题(并查集)

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例1&#xff1a; 输入 4 5 9 2 3 4 5 输出 YES 0 0 1 1 样例2&#xff1a; 输入 3 3 4 1 2 4 输出 NO 思路&#xff1a; 这道题关键点在于。 当集合中有一个元素均存在于集合 A 和集合 B 的时…

(杂项笔记)腾讯文档设置隔行换色

文档小技巧 一、在表格工具栏中选择“数据”栏二、选择新建条件格式三、进行以下设置1. 应用范围2. 条件设置3. 这是表格颜色 四、样例展示1. 隔行换色2. 隔3行换色 最近在使用某家的文档进行多人协同办公&#xff0c;遇到的一些小技巧&#xff0c;在这里分享给大家&#xff1b…