总目录:https://blog.csdn.net/treesorshining/article/details/125726400
文章目录
1.概述
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题,常常在使用中以森林来表示。正如其名,主要有查找与合并两个操作。
2.基本操作
2.1 初始化
初始化将所有结点的双亲设为-1。
// 并查集初始化操作
void Initial(int S[]) {
// 初始化将所有结点的双亲设为-1
for(int i = 0;i < SIZE;i++) {
S[i] = -1;
}
}
2.2 查(时间复杂度O(n))
查操作,找到x所属结合(即返回x所属根结点),时间复杂度O(n)。
// 查操作,找到x所属结合(即返回x所属根结点)
// 时间复杂度O(n)
int Find(int S[], int x) {
// 循环寻找x的根,根的双亲为-1,循环结束则表示已找到双亲
while(S[x] >= 0) {
x = S[x];
}
return x;
}
2.3 并(时间复杂度O(1))
并操作,将两个集合合并为一个,时间复杂度O(1)。
void Union(int S[], int Root1, int Root2) {
// Root1与Root2是相同集合则不可合并
if(Root1 == Root2) {
return;
}
// 合并即是将令一棵树称为另一棵树的子树
// 将根Root2连接到另一根Root1下面
S[Root2] = Root1;
}
2.4 并优化(查时间复杂度O(log2n))
Union操作优化
目的:减少Find操作的所需的时间
方法:在构建新树时,尽可能不让树长高(这样可以降低Find操作最坏时间复杂度)
具体实现方法:将根结点的值改为(-结点数,即负结点数),再根据此为依据,让小树合并到大树上
通过此优化树的高度不会超过log2n(向下取整) + 1
之后Find操作的最坏时间复杂度为O(log2n)
// Union操作优化
// 目的:减少Find操作的所需的时间
// 方法:在构建新树时,尽可能不让树长高(这样可以降低Find操作最坏时间复杂度)
// 具体实现方法:将根结点的值改为(-结点数),再根据此为依据,让小树合并到大树上
// 通过此优化树的高度不会超过log2n(向下取整) + 1
// 之后Find操作的最坏时间复杂度为O(log2n)
void UnionOptimize(int S[], int Root1, int Root2) {
// Root1与Root2是相同集合则不可合并
if(Root1 == Root2) {
return;
}
// 比较树的结点数,将小树合并到大树
if(S[Root2] > S[Root1]) {
// 更新树的结点数
S[Root1] += S[Root2];
// 合并
S[Root2] = Root1;
} else {
// 更新树的结点数
S[Root2] += S[Root1];
// 合并
S[Root1] = Root2;
}
}
2.5 查优化(时间复杂度O(a(n))
Find操作优化(压缩路径)
先找到根结点,再将查找路径上所有结点都挂到根结点下
在再次寻找时,只需要很短路径就能找到了
每次Find操作,先找根,再压缩路径,可使树的高度不超过O(a(n))
a(n)是一个增长缓慢的函数,对于常见n值,通常a(n)<=4
// Find操作优化(压缩路径)
// 先找到根结点,再将查找路径上所有结点都挂到根结点下
// 在再次寻找时,只需要很短路径就能找到了
// 每次Find操作,先找根,再压缩路径,可使树的高度不超过O(a(n))
// a(n)是一个增长缓慢的函数,对于常见n值,通常a(n)<=4
int FindOptimize(int S[], int x) {
int root = x;
while(S[root] >= 0) {
// 循环找到根结点
root = S[root];
}
// 压缩路径
while(x != root) {
// 令t指向x的父结点
int t = S[x];
// x直接挂到根结点下
S[x] = root;
x = t;
}
// 返回结点编号
return root;
}
3.完整代码
#include<stdio.h>
#define SIZE 13
// 通常用数组元素下标代表元素名,用根节点下标代表子集合名,根节点双亲默认设为-1
// 集合元素数组(双亲指针数组)
int UFSets[SIZE];
// 并查集初始化操作
void Initial(int S[]) {
// 初始化将所有结点的双亲设为-1
for(int i = 0;i < SIZE;i++) {
S[i] = -1;
}
}
// 查操作,找到x所属结合(即返回x所属根结点)
// 时间复杂度O(n)
int Find(int S[], int x) {
// 循环寻找x的根,根的双亲为-1,循环结束则表示已找到双亲
while(S[x] >= 0) {
x = S[x];
}
return x;
}
// 并操作,将两个集合合并为一个
// 时间复杂度O(1)
void Union(int S[], int Root1, int Root2) {
// Root1与Root2是相同集合则不可合并
if(Root1 == Root2) {
return;
}
// 合并即是将令一棵树称为另一棵树的子树
// 将根Root2连接到另一根Root1下面
S[Root2] = Root1;
}
// Union操作优化
// 目的:减少Find操作的所需的时间
// 方法:在构建新树时,尽可能不让树长高(这样可以降低Find操作最坏时间复杂度)
// 具体实现方法:将根结点的值改为(-结点数),再根据此为依据,让小树合并到大树上
// 通过此优化树的高度不会超过log2n(向下取整) + 1
// 之后Find操作的最坏时间复杂度为O(log2n)
void UnionOptimize(int S[], int Root1, int Root2) {
// Root1与Root2是相同集合则不可合并
if(Root1 == Root2) {
return;
}
// 比较树的结点数,将小树合并到大树
if(S[Root2] > S[Root1]) {
// 更新树的结点数
S[Root1] += S[Root2];
// 合并
S[Root2] = Root1;
} else {
// 更新树的结点数
S[Root2] += S[Root1];
// 合并
S[Root1] = Root2;
}
}
// Find操作优化(压缩路径)
// 先找到根结点,再将查找路径上所有结点都挂到根结点下
// 在再次寻找时,只需要很短路径就能找到了
// 每次Find操作,先找根,再压缩路径,可使树的高度不超过O(a(n))
// a(n)是一个增长缓慢的函数,对于常见n值,通常a(n)<=4
int FindOptimize(int S[], int x) {
int root = x;
while(S[root] >= 0) {
// 循环找到根结点
root = S[root];
}
// 压缩路径
while(x != root) {
// 令t指向x的父结点
int t = S[x];
// x直接挂到根结点下
S[x] = root;
x = t;
}
// 返回结点编号
return root;
}
int main() {
int temp[13] = {-1, 0, -1, -1, 1, 1, 2, 3, 3, 3, 4, 4, 7};
for(int i = 0;i < SIZE;i++) {
UFSets[i] = temp[i];
}
printf("%d\n", Find(UFSets, 0));
printf("%d\n", Find(UFSets, 3));
Union(UFSets, 0, 3);
printf("%d\n", Find(UFSets, 0));
}