目录
1.什么是集合?
在数据结构中,集合(Set)是一种不允许重复元素的数据结构。
集合中的元素是无序的,且每个元素在集合中仅出现一次。集合主要用于快速判断某个元素是否在集合中存在,以及对不同集合进行并集、交集、差集等操作。
集合通常提供以下基本操作:
1. 插入元素:将一个新元素添加到集合中,如果该元素已存在则不进行任何操作。
2. 删除元素:从集合中移除指定的元素。
3. 查找元素:判断一个元素是否在集合中。
4. 集合运算:如求两个集合的并集(包含两个集合中的所有元素)、交集(包含两个集合共有的元素)、差集(属于一个集合但不属于另一个集合的元素)等。
集合可以通过多种方式实现,比如使用数组、链表、哈希表等数据结构。
2.什么是并查集?
并查集(Union-Find)是一种特殊的数据结构,它可以用于处理一些不相交集合的合并及查询问题。
集合是一个更广泛的概念,是一组无序且不重复元素的组合。
并查集是对集合的一种具体应用和操作方式。在并查集中,每个元素都属于一个特定的集合。
并查集主要支持两个重要操作:“合并”(Union)和“查找”(Find)。
“合并”操作将两个集合合并为一个集合。
“查找”操作用于确定一个元素所属的集合。
通过高效地实现这两个操作,并查集能够在解决一些图论问题、动态连通性问题等方面发挥重要作用。
总的来说,并查集是一种基于集合概念的特殊数据结构,专注于高效地处理集合的合并和元素所属集合的查询。
并查集通常通过两种技术来优化操作效率:
- 路径压缩(Path Compression):在执行查找操作时,将访问路径上的所有节点直接连接到根节点,从而加速后续的查找操作。
- 按秩合并(Union by Rank/Size):在合并操作时,总是将较小的树合并到较大的树上,从而避免树的不平衡。
3.并查集的表示
3.1双亲表示法
双亲表示法: 孩子指向双亲。
用树结构表示集合,树的每个结点代表一个集合元素
3.2以数组形式存储
其实处理这个问题主要是往上追溯其父节点,而并不是一味的向下追寻。
所以如同上一章,我们使用数组形式的树。
4.并查集的定义
4.1类型名称:并查集(Union-Find)
数据对象集
- 一个有0个或多个元素的有穷集合。
- 每个元素属于某一个集合,集合中的元素通过树结构连接,树的根节点为集合的代表元素。
操作集
- Union-Find CreatUnionFind(int size):生成一个包含
size
个元素的并查集,每个元素初始化为其自身的父节点。 - int Find(UnionFind uf, int x):查找元素
x
所在集合的代表元素,采用路径压缩优化。 - void Union(UnionFind uf, int x, int y):将元素
x
和y
所在的集合合并,采用按秩合并优化。 - int IsConnected(UnionFind uf, int x, int y):判断元素
x
和y
是否在同一个集合中。
4.2按秩合并
随着集合元素的存入,树型的组织形式势必会让这棵树越来越高,这就导致了查找效率的降低,所以我们再进行集合的并运算时,将子树的元素个数作为其秩(优先级),秩小的子树在合并时需要挂载在秩大的子树下。
并且为了防止结构体中的空间浪费,我们可以将秩用根节点的负数表示的绝对值来代替。
在合并时直接通过比较两子树的秩来决定怎样合并,可以显著提升并查集的效率。
4.3路径压缩
可以在查询操作时将根节点下面挂载的结点直接指向根结点,这样省区了一层一层的指向,有利于我们的查找效率提高。并且由于是树型结构,且根结点下的结点本身就同属于一个集合,同时指向一个根节点省去了很多事情。
5.并查集的初始化
// 创建并查集,初始化为用户给定的数组
SetType* CreatUnionFind(ElementType* elements, int size) {
SetType* uf = (SetType*)malloc(size * sizeof(SetType));
int i;
for (i = 0; i < size; i++) {
uf[i].Data = elements[i];
uf[i].Parent = -1; // 初始化每个节点的父节点为自己,秩为1
}
return uf;
}
6.并查集的并运算
// 合并元素x和y所在的集合,按秩合并
void Union(SetType* uf, int x, int y) {
int rootX = Find(uf, x);
int rootY = Find(uf, y);
if (rootX != rootY) {
if (uf[rootX].Parent < uf[rootY].Parent) { // rootX树更高
uf[rootX].Parent += uf[rootY].Parent; // 秩相加
uf[rootY].Parent = rootX; // rootY挂在rootX下
} else { // rootY树更高或一样高
uf[rootY].Parent += uf[rootX].Parent; // 秩相加
uf[rootX].Parent = rootY; // rootX挂在rootY下
}
}
}
7.并查集的查找
// 查找元素x的根节点,路径压缩
int Find(SetType* uf, int x) {
if (uf[x].Parent < 0) {
return x; // x是根节点
} else {
uf[x].Parent = Find(uf, uf[x].Parent); // 路径压缩
return uf[x].Parent;
}
}
完整代码示例
#include <stdio.h>
#include <stdlib.h>
typedef int ElementType;
typedef struct {
ElementType Data;
int Parent; // 负数表示根节点且其绝对值为秩
} SetType;
// 创建并查集,初始化为用户给定的数组
SetType* CreatUnionFind(ElementType* elements, int size) {
SetType* uf = (SetType*)malloc(size * sizeof(SetType));
int i;
for (i = 0; i < size; i++) {
uf[i].Data = elements[i];
uf[i].Parent = -1; // 初始化每个节点的父节点为自己,秩为1
}
return uf;
}
// 查找元素x的根节点,路径压缩
int Find(SetType* uf, int x) {
if (uf[x].Parent < 0) {
return x; // x是根节点
} else {
uf[x].Parent = Find(uf, uf[x].Parent); // 路径压缩
return uf[x].Parent;
}
}
// 合并元素x和y所在的集合,按秩合并
void Union(SetType* uf, int x, int y) {
int rootX = Find(uf, x);
int rootY = Find(uf, y);
if (rootX != rootY) {
if (uf[rootX].Parent < uf[rootY].Parent) { // rootX树更高
uf[rootX].Parent += uf[rootY].Parent; // 秩相加
uf[rootY].Parent = rootX; // rootY挂在rootX下
} else { // rootY树更高或一样高
uf[rootY].Parent += uf[rootX].Parent; // 秩相加
uf[rootX].Parent = rootY; // rootX挂在rootY下
}
}
}
// 判断元素x和y是否在同一集合中
int IsConnected(SetType* uf, int x, int y) {
return Find(uf, x) == Find(uf, y);
}
// 释放并查集内存
void DestroyUnionFind(SetType* uf) {
free(uf);
}
// 显示并查集的父节点数组
void PrintUnionFind(SetType* uf, int size) {
printf("Element: ");
int i;
for (i = 0; i < size; i++) {
printf("%d ", uf[i].Data);
}
printf("\nParent: ");
int j;
for (j = 0; j < size; j++) {
printf("%d ", uf[j].Parent);
}
printf("\n");
}
int main() {
int elements[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int size = sizeof(elements) / sizeof(elements[0]);
SetType* uf = CreatUnionFind(elements, size);
// 显示初始状态
printf("Initial state:\n");
PrintUnionFind(uf, size);
// 合并集合
Union(uf, 1, 2);
Union(uf, 2, 3);
Union(uf, 4, 5);
Union(uf, 6, 7);
Union(uf, 7, 8);
// 显示合并后的状态
printf("\nAfter union operations:\n");
PrintUnionFind(uf, size);
// 检查集合
printf("\nCheck connections:\n");
printf("1 and 3 connected: %d\n", IsConnected(uf, 1, 3)); // 输出: 1
printf("1 and 4 connected: %d\n", IsConnected(uf, 1, 4)); // 输出: 0
printf("6 and 8 connected: %d\n", IsConnected(uf, 6, 8)); // 输出: 1
printf("5 and 9 connected: %d\n", IsConnected(uf, 5, 9)); // 输出: 0
// 释放内存
DestroyUnionFind(uf);
return 0;
}