并查集的思想就是集合,本质上是很简单的,利用数组就可以实现简单版的,我们需要一个辅助数组pre,这个数组存储当前元素的前驱或者说双亲下标,当一个元素属于同一个集合的时候,这几个元素的前驱下标就等于所属集合中前驱最小的值,计算某一个元素所属的集合简单版的时间复杂度为O(n),在合并操作中,简单的判断两个元素的根值是否一致,一致的话说明就是一个集合就不需要合并,如果不一致,就大的元素集合合并到小的元素集合在克鲁斯卡尔算法中也会用到并查集。
下面是完整实现代码,我们将创建三个集合:
#include<stdio.h>
#include<stdlib.h>
#define size 11 // 定义集合的大小
// 初始化集合,将每个元素的父节点指针设置为-1
void InitSet(int pre[]) {
int i = 0;
for (; i < size; i++) {
pre[i] = -1; // -1 表示该元素是集合的根节点
}
}
// 查找元素x所在集合的根节点
int Find(int pre[], int x) {
while (pre[x] >= 0) { // 遍历直到找到根节点
x = pre[x];
}
return x; // 返回根节点
}
// 合并两个集合,root1/2输入的是根下标,不是pre的下标
void UnionSet(int pre[], int root1, int root2) {
if (root1 == root2) { // 如果两个根节点相同,则已经在同一集合中
return;
}
pre[root1] += pre[root2]; // 将root2的大小累加到root1
pre[root2] = root1; // 将root2的根指向root1,root1成为新的根节点
}
// 打印集合的当前状态
void SetPrint(int s[]) {
int i = 0;
printf("\n");
for (; i < size; i++) {
printf("%2d ", s[i]);
}
}
// 打印预处理数组pre的当前状态
void PrePrint(int pre[]) {
int i = 0;
printf("\nPre数组的值为:\n");
for (; i < size; i++) {
printf("%2d ", pre[i]);
}
printf("\n");
}
// 查找并打印每个元素所属的集合
void FindPreIndex(int pre[]) {
printf("所有元素所属的集合为:\n");
for (int i = 0; i < size; i++) {
printf("%d:%d ", i, Find(pre, i)); // 打印元素i及其所属的集合根节点
}
}
// 查找优化:路径压缩技术,使查找时间复杂度接近O(1)
int FindOptimize(int pre[], int x) {
int root = x;
// 找到x的根节点
while (pre[root] >= 0) {
root = pre[root];
}
// 压缩路径
while (x != root) {
int t = pre[x];
pre[x] = root; // 将x的父节点直接指向根节点
x = t;
}
return root; // 返回根节点
}
// 合并优化:按大小合并集合,时间复杂度接近O(log2n)
void UnionOptimize(int pre[], int root1, int root2) {
if (root1 == root2) { // 如果两个根节点相同,则已经在同一集合中
return;
}
if (pre[root1] < pre[root2]) {
pre[root1] += pre[root2]; // 将root2的集合大小累加到root1
pre[root2] = root1; // 将root2的根指向root1,root1成为新的根节点
}
else {
pre[root2] += pre[root1]; // 将root1的集合大小累加到root2
pre[root1] = root2; // 将root1的根指向root2,root2成为新的根节点
}
}
// 主函数
int main() {
int s[] = { 0,1,2,3,4,5,6,7,8,9,10 }; // 初始化集合元素
int* pre = (int*)malloc(sizeof(int) * size); // 动态分配内存
InitSet(pre); // 初始化集合
// 手动构建三个集合
pre[1] = 0;
pre[2] = 0;
pre[3] = 0;
pre[0] = -5; // 集合1的大小为5
pre[5] = 4;
pre[6] = 4;
pre[7] = 4;
pre[4] = -4; // 集合2的大小为4
pre[9] = 8;
pre[8] = -2; // 集合3的大小为2
pre[10] = 0; // 元素10的根节点为0,属于集合1
printf("初始化集合后Pre值为:");
SetPrint(pre); // 打印初始化后的集合状态
// 合并集合2到集合1
UnionSet(pre, 0, 4);
printf("\n集合2合并到集合1");
PrePrint(pre); // 打印合并后的集合状态
// 优化查找
printf("优化查找:\n");
printf("\nFindOptimize-5:%d", FindOptimize(pre, 5)); // 查找元素5的根节点
printf("\nFindOptimize-9:%d", FindOptimize(pre, 9)); // 查找元素9的根节点
// 合并集合3到集合1
printf("\n集合3合并到集合1");
UnionOptimize(pre, 0, 8);
PrePrint(pre); // 打印合并后的集合状态
}
运行结果: