并查集总结
在一些N元素集合问题中,通常在开始时让每个元素构成一个单元素集合,然后按一定顺序将元素合并,数据量大时,应用并查集描述节省时间与空间。
并查集是一种树形数据结构,用于处理一些不相交的集合合并与查询问题。通常支持两种操作:
合并(Union):把两个不相交的集合合并为一个集合。
查询(Find):查询两个元素是否在同一个集合,通常依据一个集合的代表元素。
这是一个树状结构,要寻找集合的代表元素,只需要一层一层往上访问父节点,直到达到树的根节点,树的根节点的父节点是自己。
初始化
开始时每个元素自成一派;
int parent[MAX];
void Build(){
for(register int i=1;i<=n;i++){
parent[i]=i;
}
}
查询
可以采用递归和循环的方式,我这里采用递归。`
inline int Find(int x){
if(parent[x]!=x){//只有根节点的parent为自身
return Find(parent[x]);
}
return x;
}
合并
inline void Union(int x,int y){
parent[Find(x)]=Find(y);//实质是将一个集合挂到另一个集合上
}
但上面的操作执行之后,我们很容易发现所构成的树形结构成了单支树,这样会导致查找效率不高,因此产生了一种路径压缩的办法,其核心思想就是尽量减少树的高度:将每个结点的父节点直接设为集合的代表元素。
在Find时进行路径压缩
int Find_2(int x){
if(parent[x]!=x){
parent[x]=Find(parent[x]);
}
return parent[x];
}
通常可以简化为:
int Find_3(int x){
return parent[x]==x ? parent[x] : (parent[x]=Find(parent[x]));//?:优先级比=高,加()
}
很多人可能有个误解,以为路径压缩后,并查集都是一个二层树结构,事实上,路径压缩只在查询时进行,在一般情况下,并查集还是比较复杂的结构。
那如果我们有一个复杂的树和单元素集合合并,那我们应选哪个集合的代表元素呢?
显然我们的原则是尽量不增加树的高度。
基于树的高度我们增加一个rank数组,用于记录一个树的高度。
按秩合并
初始化
void Build(){
for(int i=1;i<=n;i++){
parent[i]=i;
rank[i]=1;
}
}
合并(按秩合并)
void Union(int x,int y){
x=Find(x);
y=Find(y);
if(x==y) return;
if(rank[x]<rank[y]){
parent[x]=y;
}else{
parent[y]=x;
if(rank[x]==rank[y]){
rank[x]++;
}
}
}
通常在代码中,选用一种优化方案即可。