并查集【数据结构与算法】【C语言版-笔记】

目录

一、需求分析

假设有n个互不相交的集合

◼问题1:给定某个集合中的一个元素,查找该元素属于哪个集合?

◼问题2:如何合并两个集合?

复制代码
实例:
有n个村庄,有些村庄之间有连接的路,有些没有(有路连接的村庄视为同一个集合)
设计一个数据结构,能快速的执行下面两个操作
◼查询2个村庄之间是否有连接的路
◼连接两个的村庄

若使用数组、线性链表等其两者操作的复杂度至少是O(n),而并查集能够办到查询、连接的均摊时间复杂度都是O(log2n),非常适合解决这类"连接"相关的问题。

二、并查集

顾名思义,并查集是一种对集合以及集合里元素操作的结构,其中"并"就是合并,"查"就是查询某元素属于哪个集合,这也是它的两大核心操作

复制代码
◼查找(Find):查找元素所在的集合(这里的集合指的是逻辑上(数学上)的集合而不是具体某个特定集合)
◼合并(Union):将两个元素所在的集合合并为一个集合

如何选择并查集的存储结构?从树的形状出发,我们知道一棵树上的叶子必定属于同一棵树(额,这好像是废话),把树看作集合,叶子作为集合的元素。给定一片叶子,找到它对应的树干(或根)就可以确定叶子的归属。数据结构中树的常用存储结构有三种方式:【双亲表示法,孩子表示法,孩子兄弟表示法】我们选择双亲表示法,因为这种设计方法很适合找到根结点,从而很容易确定元素所属集合。对于合并集合,我们只需要修改其中一个集合的parent指向另一个集合的 "根" 即可。

cpp 复制代码
双亲表示法:
给定一块连续的内存空间存货结点,给每个结点附设一个指针器,指示它的双亲结点在存储空间中的位置
其存储表示如下:
#define MAX_TREE_SIZE 20
typedef int TElemType;
typedef struct {
	TElemType num;
	int parent;
} TNode;
typedef struct {
	TNode nodes[MAX_TREE_SIZE];
	int length;//结点数
} PTree

//并查集的存储表示(为了方便直接使用数组(集合为数集),且num既是集合中的元素也是结点本身在存储空间中的位置)
typedef TNode unionFind[MAX_TREE_SIZE];

(备注:为了方便直接使用数组(集合为数集),且num即是集合中的元素也是结点本身在存储空间中的位置)

三、代码实现

规定:用一块连续的内存空间存储n个集合的全部元素,每个集合对应一棵树,树的根结点的parent值记为 -1

3.1 Find函数

cpp 复制代码
//查找元素e所属的集合,即找到该集合的"根"元素,返回根元素
TElemType* Find(unionFind uf,TElemType e){
	int root=e;//当前元素本身
	int x=uf[e].parent;//e的双亲所在的位置
	while(x!=-1){
		root=x;
		x=uf[x].parent;
	}
	return root;
}

时间复杂度

最坏:O(n)、平均:O(log2n)、最好:O(1)

3.2 Union函数

cpp 复制代码
//合并两个集合,rooti(i=1,2)为集合的根
void Union(unionFind uf,int root1,int root2){
	if(root1==root2) return;//如果是同一个集合,不用合并
	uf[root2].parent=root1;
}

时间复杂度O(1)

3.3 优化1

在进行Find的时,发现查找的长度和树的高度有关,且最坏时间复杂度是O(n),这时棵树变成了单支树(退化成为链表)。因此,可以从树高的角度出发减少树高从而达到优化Find的时间效率。优化思路是在每次Union的时候,尽可能的让树 "不长高",让小的树合并到大的树(树的大小指的是结点数量的多少)或者是让矮的树合并到高树上。下面采用第一种,这种方法需要有一个计数器保存一个集合(树)的大小,我们直接利用一个集合的"根"的parent来保存集合的大小,同时为了保留根的标识(parent为-1),parent==集合大小的相反数

cpp 复制代码
void Union(unionFind uf,root1,root2){
	if(uf[root1].parent<uf[root2].parent){
		uf[root1].parent+=uf[root2].parent;
		uf[root2].parent=root1;
	}else{
		uf[root2].parent+=uf[root1].parent;
		uf[root1].parent=root2;
	}n
	
}

优化后,Find最坏的时间复杂度变为:O(log2n)
(第二种方法---矮树合并到高树上,如何实现,大家可以自行思考)

3.4 终极优化2---压缩策略

所谓压缩是指压缩树的高度。我们对Find操作进行优化,当执行Find时,我们把传入的元素到根的路径上的结点(包含元素本身所在的结点)全部都直接挂在根的孩子位置上(如果已经是根的孩子了,则不用修改)。当再次查找该元素所属集合时,只需要常数级别的操作就可以完成查找。

cpp 复制代码
void Find(unionFind uf,TElemType e){
	int root=e;//当前元素本身
	int x=uf[e].parent;//当前元素的双亲位置
	//找到元素所在集合的"根"
	while(x>=0){
		root=x;
		x=uf[e].parent;
	}
	while(e!=root){
		int t=uf[e].parent;//t表示路径上元素的位置,t等价uf[uf[e].parent].num,前面黄字部分已表明
		uf[e].parent=root;
		e=uf[t].num;
	}
	return root;
}

如下图,调用Find后

优化后时间复杂度

最坏: O ( α ( n ) ) O(\alpha(n)) O(α(n)),其中, α ( n ) \alpha(n) α(n)是比log2n增长速度小的数量级

相关推荐
智者知已应修善业4 小时前
【51单片机8位数码管动态显示日期小数点风格】2023-11-13
c++·经验分享·笔记·算法·51单片机
智者知已应修善业4 小时前
【51单片机有三个LED 分别第一个灯闪三下 再到第二个灯又闪三下 再到第三个灯又闪三下 就这样循环程序】2023-11-16
c++·经验分享·笔记·算法·51单片机
暴躁小师兄数据学院4 小时前
【AI大数据工程师特训笔记】第04讲:PostgreSQL 数据库内置函数详解
大数据·数据库·笔记·ai·语言模型
小娄~~6 小时前
C语言卷子错题集
c语言·开发语言·数据结构
z落落7 小时前
C# 数组 最终完整版全套笔记(一维+多维+交错+引用类型+对象数组)
java·笔记·c#
searchforAI7 小时前
B站视频转笔记用哪个工具?2026年四款AI笔记工具对比实测
人工智能·经验分享·笔记·gpt·学习·视频总结·ai笔记
过期动态7 小时前
【LeetCode 热题 100】盛最多水的容器
java·数据结构·spring boot·算法·leetcode·spring cloud·职场和发展
RainCity8 小时前
Java Swing 自定义组件库分享(九)
java·笔记·后端
05候补工程师8 小时前
【408考研·数据结构专题】二叉树、树与森林、线索树及哈夫曼树核心考点与秒杀技巧深度总结
数据结构·经验分享·笔记·考研·算法
Dlrb12119 小时前
数据结构-队列,循环队列,哈希表
数据结构·哈希表·队列·循环队列·哈希存储