并查集最开始是将N个元素视为N个单独的集合,然后按照一定的顺序将属于同一组的元素进行合并,可以将每个集合看成一棵树,所有的集合则构成了一个森林。查找和合并的过程也可以看成是在树上进行的相关操作。其中利用到了两个较为重要的方法以提高效率:路径压缩和按秩归并。
下面将整个并查集的操作封装在了一个类中(disjointset)
1、成员变量:
vector<int> father;//每个集合的代表节点
vector<int> _size;//每个集合的大小
int _count;//集合的个数
2、构造函数
将初始时的N个元素视为N个单独的集合,自己便是自己所在集合的代表元素
disjointset(int n)
{
father.resize(n);
size.resize(n);
for (int i=0;i<n;i++)
{
father[i]=i;
size[i]=1;
}
_count=n;
}
3、Find函数(尾递归和循环)
尾递归在执行的时候会自动转为循环进行执行,因此两种方法其实是等价的。
int Find1(int a)
{
while (a!=father[a])
{
father[a]=father[father[a]];
a=father[a];
}
return a;
}
int Find2(int a)
{
if (a==father[a])
return a;
return father[a]=Find2(father[a]);
}
这其中便利用到了路径压缩的方法,在向上查找代表节点的时候更新father数组,意在使得在之后的查找中效率更高。
4、Union函数
void _union(int a,int b)
{
int fa=Find1(a);
int fb=Find1(b);
if (fa==fb)
return;
if (_size[fa]<_size[fb])
{
father[fa]=fb;
_size[fb]+=_size[fa];
}
else
{
father[fb]=fa;
_size[fa]+=_size[fb];
}
_count--;
}
此函数中利用到了按照集合的大小进行归并,即每次都是将更小的集合归并到更大的集合之中,这样处理使得整个树在形状上更加的扁平,以免树的高度过大,为查找的效率提供了保证。