1. 什么是并查集?
并查集(Disjoint Set Union,简称DSU)是一种树型的数据结构,用于处理一些不交集合(Disjoint Sets)的合并及查询问题。它支持两种操作:
-
Find:查询某个元素属于哪个集合
-
Union:将两个集合合并为一个集合
并查集在解决连通性问题、图论算法(如Kruskal最小生成树算法)等方面有广泛应用。
2. 基本实现
2.1 数据结构表示
在代码中,我们使用一个数组S[]
来表示并查集:
int UFSets[SIZE]; // 并查集数组
数组元素的含义:
-
如果
S[i]
为负数:表示i
是某个集合的根,其绝对值表示该集合的大小 -
如果
S[i]
为非负数:表示i
的父节点是S[i]
2.2 初始化
void Initial(int S[]){
for(int i=0;i<SIZE;i++){
S[i] = -1; // 每个元素初始化为独立的集合,大小为1(用-1表示)
}
}
2.3 基本合并操作(Union)
void Union(int S[],int Root1,int Root2){
if(Root1==Root2) return; // 已经是同一个集合
S[Root2] = Root1; // 直接将Root2挂到Root1下
}
这种简单的合并方式可能导致树的高度过大,影响查询效率。
2.4 基本查找操作(Find)
int Find(int S[],int x){
while(S[x]>=0){ // 如果不是根节点
x = S[x]; // 向上查找父节点
}
return x; // 返回根节点
}
3. 优化策略
3.1 按大小合并(Union by Size)
为了避免树过高,我们可以在合并时总是将较小的树合并到较大的树下:
void Union1(int S[],int Root1,int Root2){
if(Root1==Root2) return;
if(S[Root2]>S[Root1]){ // Root1的集合更大(注意S[]存储的是负数)
S[Root1]+=S[Root2]; // 合并集合大小
S[Root2] = Root1; // 小树合并到大树
}else{
S[Root2]+=S[Root1];
S[Root1] = Root2;
}
}
为什么比较的是S[Root2]>S[Root1]
?
-
因为
S[root]
存储的是负数,更大的值(更接近0)表示更小的集合 -
例如:
S[Root1]=-5
(集合大小为5),S[Root2]=-3
(集合大小为3) -
-3 > -5
,所以Root1
的集合更大
3.2 路径压缩(Path Compression)
在查找操作时,我们可以将路径上的所有节点直接连接到根节点,以减小树的高度:
int Find1(int S[],int x){
int root = x;
while(S[root]>=0){ // 先找到根节点
root = S[root];
}
// 路径压缩:将路径上的所有节点直接连接到根节点
while(x!=root){
int t = S[x]; // 保存父节点
S[x] = root; // 当前节点直接指向根
x = t; // 处理父节点
}
return root;
}
4. 复杂度分析
-
未优化版本:
-
Find:最坏O(n)
-
Union:O(1)
-
-
优化后版本:
-
Find:接近O(α(n))(其中α是反阿克曼函数,增长极其缓慢)
-
Union:接近O(α(n))
-
在实际应用中,可以认为优化后的并查集操作是常数时间的。
5. 完整代码示例
#include<bits/stdc++.h>
using namespace std;
#define SIZE 100
int UFSets[SIZE];
// 初始化
void Initial(int S[]){
for(int i=0;i<SIZE;i++){
S[i] = -1;
}
}
// 基本合并操作
void Union(int S[],int Root1,int Root2){
if(Root1==Root2) return;
S[Root2] = Root1;
}
// 基本查找操作
int Find(int S[],int x){
while(S[x]>=0){
x = S[x];
}
return x;
}
// 优化合并操作(按大小合并)
void Union1(int S[],int Root1,int Root2){
if(Root1==Root2) return;
if(S[Root2]>S[Root1]){ // Root1的集合更大
S[Root1]+=S[Root2];
S[Root2] = Root1;
}else{
S[Root2]+=S[Root1];
S[Root1] = Root2;
}
}
// 优化查找操作(路径压缩)
int Find1(int S[],int x){
int root = x;
while(S[root]>=0){
root = S[root];
}
while(x!=root){
int t = S[x];
S[x] = root;
x = t;
}
return root;
}
int main(){
Initial(UFSets);
// 使用示例...
return 0;
}
6. 应用场景
-
图的连通性问题:判断图中两个节点是否连通
-
Kruskal最小生成树算法:用于判断边是否会形成环
-
社交网络中的好友关系:判断两个人是否属于同一个社交圈
-
图像处理:像素连通区域分析
7. 总结
并查集是一种高效处理不相交集合合并与查询问题的数据结构。通过按大小合并和路径压缩两种优化策略,可以显著提高其性能。理解并查集的关键在于掌握数组表示法的含义以及两种优化策略的原理。在实际应用中,优化后的并查集几乎是常数时间复杂度,非常适合处理大规模的集合合并与查询问题。