C++ 实现并查集结构

news2025/7/8 12:46:41

前言

并查集一般用于多元素,多集合的查找问题;
听说很有用,但是平时好像确实没有怎么见过。。
leetcode典型例题:岛屿数量

一、原理

  • 其实并查集的每个小集合就是一张有向图,只不过是所有子节点指向父节点的图结构。
    在这里插入图片描述
    他之所以能够高效的合并和查找,是因为它在查找过程中,一直在动态更改所有走过节点的父节点。

主要结构:

这里先定义一个节点结构:

template<class T>
class EleNode
{
public:
	T value;
	EleNode<T>* father;
	EleNode(T val)
	{
		value = val;
		father = nullptr;
	}
};
  • 该节点结构非常类似于链表
    只不过它里面存的指针指向自己的爸爸

主结构中:

  • nodeMap根据用户数据存储对应节点数据,所有被创建出来的节点都被存放在里面
  • numMap仅用于记录该集合的元素数量(只记录头部元素,因为这个数据只需要一条)
  • void createNode(T val)函数中,创建节点需要在nodeMapnumMap中初始化
template<class T>
class UnionFindSet
{
	//节点记录
	unordered_map<T, EleNode<T>*> nodeMap;
	//元素集数量记录
	unordered_map<EleNode<T>*, int> numMap;
public:
	UnionFindSet(){}
	//构造函数
	UnionFindSet(const vector<T>& list)
	{
		for (int i = 0; i < list.size(); i++)
		{
			createNode(list[i]);
		}
	}
	//销毁节点
	~UnionFindSet()
	{
		for (auto ele : nodeMap)
		{
			delete ele.second;
		}
	}

	// 新建一个节点
	void createNode(T val)
	{
		if (nodeMap.find(val) != nodeMap.end()) return;
		EleNode<T>* newNode = new EleNode<T>(val);
		nodeMap.insert(make_pair(val, newNode));
		numMap.insert(make_pair(newNode, 1));
	}
}

主要方法:

有三个方法,分别为:

// 判断是否在同个集合中
bool isSameSet(const T& v1, const T& v2);
// 执行联合,即合并节点
void doUnion(EleNode<T>* t1, EleNode<T>* t2);
//找头节点
EleNode<T>* findHead(EleNode<T>* node);
  1. 判断是否在同个集合中
    判断两个节点的头节点是不是同一个。
    为啥要找到头节点?
    其实根据刚才的那张图就很显而易见
  • 如果两个节点在同一个集合中,那么他们两个一直执行查找父亲的操作;最后绝对能找到同一个头节点
  • 如果两个节点不在同集合中,那么执行该操作过后;最后绝对找到不同的头节点
	// 是否为同个集合
	bool isSameSet(const T& v1, const T& v2)
	{
		assert(nodeMap.find(v1) != nodeMap.end() && nodeMap.find(v2) != nodeMap.end());
		return findHead(nodeMap[v1]) == findHead(nodeMap[v2]);
	}
  1. 合并节点
  • 将节点数量较小的那个集合,它的头部节点的指针指向节点数量较大集合的头节点。
    这实际上也是两个图结构的合并。至于为啥要选出节点少的一边,这个跟并查集的优化逻辑有关,放在下面的方法说。

1> 首先判断他们是否已经在同个集合中,在同集合中就跳出。
2> 再分别找到他们两个的集合数量中的较大值和较小值
3> 将数量较小的一方并入数量较大的一方,通过将较小集合头节点的father指向改为较大集合头部
4> 更新集合数量值
在这里插入图片描述

  • 比如上图中,用户输入2和4时,应该怎么操作?
    实际上就是直接将1号指针指向3号。
    改完以后:
    在这里插入图片描述
	// 执行联合
	void doUnion(EleNode<T>* t1, EleNode<T>* t2)
	{
		// 判断头节点并保存
		EleNode<T>* head1 = findHead(t1);
		EleNode<T>* head2 = findHead(t2);
		if (head1 == head2) return;

		//找较大较小集合
		EleNode<T>* big = numMap[head1] >= numMap[head2] ? head1 : head2;
		EleNode<T>* small = numMap[head1] >= numMap[head2] ? head2 : head1;
		//改头
		small->father = big;
		//数值更新
		numMap[big] = numMap[big] + numMap[small];
		numMap.erase(small);
	}
public:
	// 执行联合外部接口
	void doUnion(const T& v1, const T& v2)
	{
		assert(nodeMap.find(v1) != nodeMap.end() && nodeMap.find(v2) != nodeMap.end());
		doUnion(nodeMap[v1], nodeMap[v2]);
	}
  1. 找头节点
  • 找头节点的操作不仅仅是找到头部,还包含了一个重要的优化
  • 这个优化就是将所有走过的,非头节点全部直接连在头结点上
  • 并查集中,一个集合(图)最理想的状态就是所有子节点全部直接指向头节点,这种情况下,从子节点向上寻找头节点的代价是O(1)

例:从2位置开始,找到集合头部
在这里插入图片描述
执行后:
在这里插入图片描述

  • 此时集合内节点数量未改变不需要调整,只需要调整结构即可

下面的函数中,将所有走过的路径全部压入栈内,并在找到头节点后,挨个将他的父亲改为头节点,最后返回头部。

//找头
	EleNode<T>* findHead(EleNode<T>* node)
	{
		stack<EleNode<T>*> path;
		while (node->father != nullptr)
		{
			path.push(node);
			node = node->father;
		}
		while (!path.empty())
		{
			path.top()->father = node;
			path.pop();
		}
		return node;
	}

二、全部代码

#include<vector>
#include<stack>
#include<unordered_map>
#include<iostream>
#include<cassert>
using namespace std;

template<class T>
class EleNode
{
public:
	T value;
	EleNode<T>* father;
	EleNode(T val)
	{
		value = val;
		father = nullptr;
	}
};

template<class T>
class UnionFindSet
{
	//节点记录
	unordered_map<T, EleNode<T>*> nodeMap;
	//元素集数量记录
	unordered_map<EleNode<T>*, int> numMap;

	//找头
	EleNode<T>* findHead(EleNode<T>* node)
	{
		stack<EleNode<T>*> path;
		while (node->father != nullptr)
		{
			path.push(node);
			node = node->father;
		}
		while (!path.empty())
		{
			path.top()->father = node;
			path.pop();
		}
		return node;
	}

	// 执行联合
	void doUnion(EleNode<T>* t1, EleNode<T>* t2)
	{
		EleNode<T>* head1 = findHead(t1);
		EleNode<T>* head2 = findHead(t2);
		if (head1 == head2) return;

		//合并
		EleNode<T>* big = numMap[head1] >= numMap[head2] ? head1 : head2;
		EleNode<T>* small = numMap[head1] >= numMap[head2] ? head2 : head1;
		small->father = big;
		numMap[big] = numMap[big] + numMap[small];
		numMap.erase(small);
	}
public:
	UnionFindSet(){}
	//构造函数
	UnionFindSet(const vector<T>& list)
	{
		for (int i = 0; i < list.size(); i++)
		{
			createNode(list[i]);
		}
	}
	//销毁节点
	~UnionFindSet()
	{
		for (auto ele : nodeMap)
		{
			delete ele.second;
		}
	}

	// 新建一个节点
	void createNode(T val)
	{
		if (nodeMap.find(val) != nodeMap.end()) return;
		EleNode<T>* newNode = new EleNode<T>(val);
		nodeMap.insert(make_pair(val, newNode));
		numMap.insert(make_pair(newNode, 1));
	}

	// 判断是否在同个集合中
	bool isSameSet(const T& v1, const T& v2)
	{
		assert(nodeMap.find(v1) != nodeMap.end() && nodeMap.find(v2) != nodeMap.end());
		return findHead(nodeMap[v1]) == findHead(nodeMap[v2]);
	}

	// 执行联合外部接口
	void doUnion(const T& v1, const T& v2)
	{
		if (!isSameSet(v1, v2))
			doUnion(nodeMap[v1], nodeMap[v2]);
	}
};

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

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

相关文章

认证鉴权对于 API 网关的重要性

认证鉴权作为 API 网关不可或缺的能力&#xff0c;已然成为用户在选型 API 网关时考量的重要因素之一。 作者钱勇&#xff0c;API7.ai 开发工程师&#xff0c;Apache APISIX Committer 在当下云原生越发成熟的环境下&#xff0c;API 网关最核心的功能可以概括为&#xff1a;连接…

高品质蓝牙耳机排行榜,值得入手的四款蓝牙耳机分享

2023年即将到来&#xff0c;还有哪些蓝牙耳机值得大家购买呢&#xff1f;蓝牙耳机在我们日常生活中发挥着很大作用。无论是听歌还是通话&#xff0c;又或者是运动健身、玩游戏等&#xff0c;都常见大家使用。同样也伴随着蓝牙耳机市场的壮大&#xff0c;五花八门的耳机层出不穷…

【C语言进阶】进来抄作业,完善你的通讯录(软工期末大作业可用)

目录 &#x1f970;前言&#x1f970;&#xff1a; 一、输入合法性检测&#x1f920;&#xff1a; ①.对“ 联系方式 ”的合法性检测&#xff1a; ②.对“年龄”进行合法性检测&#xff1a; 二、字典排序&#x1f911;&#xff1a; 三、反馈优化&#x1f92f;&#xff1a; …

Python 圣诞树代码

一、前言 1.本章将会讲解Python编程 实现圣诞树效果&#xff01; 2.圣诞节介绍 基督教纪念耶稣诞生的重要节日。亦称耶稣圣诞节、主降生节&#xff0c;天主教亦称耶稣圣诞瞻礼。耶稣诞生的日期&#xff0c;《圣经》并无记载。公元336年罗马教会开始在12月25日过此节。12月25日…

现在转行计算机如49年入国军?

阿里&#xff0c;腾讯等互联网大厂最近不太安宁&#xff0c;裁员消息频出&#xff0c;无风不起浪&#xff0c;裁员年年有&#xff0c;今年特别多。于是不少打算入行或者已经入行计算机的同学开始担忧&#xff0c;如今入行计算机&#xff0c;怎么有点49年入国军的赶脚&#xff1…

Android面试题及答案整理(2023最新版)持续更新中......

倒霉的时候总会想起福祸相依&#xff0c;但你会发现倒霉起来没完没了&#xff0c;就是看不到传说中的“福” 年初被裁了&#xff0c;我会安慰自己&#xff0c;此处不留爷自有留爷处&#xff0c;然后踏入找工作的行列&#xff1b;没有面试邀请&#xff0c;我会告诉自己&#xf…

倒在转“码”路上的文科生,文科生也能转行做IT吗?

近期&#xff0c;一篇《倒在转“码”路上的文科生》火了。 这也让关于“文科转码”的话题引起了一番热度&#xff0c;由于大环境不佳&#xff0c;使得毕业生就业成为了一个难度&#xff0c;其中文科生相较而言&#xff0c;选择更少&#xff0c;因此&#xff0c;让不少学子有了…

Java项目:springboot酒店宾馆管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 功能介绍 springboot酒店宾馆管理系统。该系统为后管系统&#xff0c;无前台。主要分三种角色&#xff1a;管理者/工作人员/前台人员。 主要功能有&…

网络安全方向好吗?

我就在这个行业&#xff0c;今年刚毕业&#xff0c;民办二本&#xff0c;目前武汉&#xff0c;薪资就没必要说了&#xff0c;高就对了。 这个行业优势就是工资高&#xff0c;缺点就需要一直学&#xff0c;卷得要死&#xff0c;不是跟别人卷&#xff0c;而是自己卷&#xff0c;…

数据库实验3 完整性语言实验

实验3 完整性语言实验 实验3.1实体完整性实验 1.实验目的 掌握实体完整性的定义和维护方法。 2.实验内容和要求 定义实体完整性,删除实体完整性。能够写出两种方式定义实体完整性的SQL 语句:创建表时定义实体完整性﹑创建表后定义实体完整性。设计SQL语句验证完整性约束是…

LeetCode 96. 不同的二叉搜索树

LeetCode 96. 不同的二叉搜索树 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#x…

我,30多岁的土木工程人,终于转行了

行外人可能没法想象&#xff0c;十年前最火爆、高校录取分数最高的土木工程专业&#xff0c;现在在贴吧知乎等社交网站上&#xff0c;竟然成了一个“劝退率”最高的专业。 土木出身的工程人&#xff0c;一边吐槽“土木毁我青春”&#xff0c;一边苦口婆心的劝退还在上学的学弟学…

一本通 1276:【例9.20】编辑距离

看完题目后&#xff0c;整个人都懵了&#xff0c;这题咋整&#xff1f; 哎呀&#xff0c;知道知道&#xff0c;用动态规划做 不要慌&#xff0c;我们慢慢分析.... 目录 做题前须知 状态转移 如果 a[i] b[j] 如果 a[i] ! b[j] 做删除操作 做插入操作 做替换操作 初始…

跑步热来袭!缤跃关注运动健康生活,跨界推出差异化酒店产品!

近期&#xff0c;人民数据研究院发布《2022全民跑步运动健康报告》&#xff0c;报告中显示参与跑步人群的年龄跨度随着社会对跑步运动不断攀升的热情而增加。现代生活节奏加快、竞争压力大使得部分中青年通过运动寻求解压&#xff0c;2022年18-40岁的跑者开始成为中坚力量&…

非零基础自学Golang 第15章 Go命令行工具 15.1 编译相关指令 15.1.3 install 15.1.4 交叉编译

非零基础自学Golang 文章目录非零基础自学Golang第15章 Go命令行工具15.1 编译相关指令15.1.3 install15.1.4 交叉编译第15章 Go命令行工具 15.1 编译相关指令 15.1.3 install go install命令的作用是编译后安装&#xff0c;该命令依赖于GOPATH&#xff0c;因此不能在独立的…

网上到处转行编程成功的,现实中真的容易吗?

首先&#xff0c;我先回答&#xff0c;转行编程真的这么简单么&#xff1f;答案是极其简单&#xff0c;但是也非常艰难。 这是一句正确的废话&#xff01; 其实&#xff0c;网上到处都是转行成功的案例。而我们现在也是在互联网上咨询&#xff0c;所以得到的答案会是什么答案…

转行IT,你需要了解的真实项目研发流程是怎样的?

本文以我在阿里写bug的项目流程为例子&#xff0c;介绍软件项目的研发一般流程&#xff0c;也可以作为企业开发流程的参考&#xff0c;让想转行IT的同学提前心里有个数。 一、职位分工 一般的大厂或者互联网软件公司&#xff0c;都会有如下职位。 1、产品经理 负责产品的设计&a…

json-server的学习笔记

文章目录json-server简介1、入门环境依赖安装2、基本使用2.1 启动jsonserver2.2 **json-server相关配置参数**2.3 jsonserver中的请求方法的作用3、筛选过滤4、分页5、排序6、切片(分页)7、特殊符号8、全文搜索9、关系10、数据库11、主页12、附加功能12.1 静态文件服务器12.2 替…

青少年等级考试【Python通关干货知识点】(一级)

青少年等级考试【Python通关干货知识点】&#xff08;一级&#xff09; 1. 编程模式 1&#xff09;交互式编程 在交互式环境的提示符>>>下&#xff0c;直接输入代码&#xff0c;按回车&#xff0c;就可以立刻得到代码执行结果。 交互式编程缺憾是没有保存下来&#x…

转行大数据,编程学Java还是Python?

Python和Java&#xff0c;是大数据行业最常见的两种编程语言&#xff0c;对于想转行大数据的人来说&#xff0c;学习哪个语言是比较好的选择呢&#xff1f; Python和大数据&#xff1a; Python本身的特点是高效率的开发和简单的维护&#xff0c;大数据运维领域也在普遍采用Pyth…