1、未优化
1、p[x]
表示x
的父节点是谁。 初始化时,将其父节点初始化为他本身,即p[x] = x
2、当要合并两个集合时,可以将每一个集合看成是一棵树,每棵树至少存在一个根节点(即本身),当合并两个不相同的集合时,一棵树会成为另一颗树的子树(儿子)。
3、合并x
所在集合与y
所在集合的方法是找到x
的祖宗节点和y的祖宗节点,然后让x
的祖宗节点成为y
的祖宗节点的儿子节点。
4、找到一个x
的祖宗节点的方法是,不断递归x
的父节点p[x]
,直到x=p[x]
,则说明,此时p[x]
就是原x的祖宗节点了,返回p[x]
即可。
注意:若数据太大或者太刁钻可以改用循环迭代来寻找,本文暂不提及。
↓ 未优化前,裸的并查集模板 ↓
int p[N]; // p[x] 表示 x 的父节点是谁
int get ( int x )
{
if ( x == p[x] ) return x; // 因为这里 x 已经等于 p[x] 了,所以返回x或p[x]都可以
return get ( p[x] );
}
void marge ( int x, int y )
{
x = get ( x ), y = get ( y );
p[x] = y; // 将 子树x 连到 根y 的下面
}
请不要使用未优化的并查集模板,这里写出来只是为了对理解有帮助
2、优化版
下面对两种操作进行优化
优化一:优化查找(get)
查找的优化是路径压缩,大大提高了优化效率
我们可以看到,我们每次get一个x,它都会遍历它的父节点,若我们重复get,则他会重复遍历,这大大降低了效率。
如果我们可以在他返回的时候将他路径上的每个节点的父节点全部改为根节点,则下一次查找时,可不用再次逐一查询。
想实现这个方式只需让p[x]等于返回来的根节点的值:p[x] = get ( p[x] )
优化后的get函数如下:
int get ( int x )
{
if ( x == p[x] ) return x;
return p[x] = get ( p[x] );
}
优化二:优化合并(marge)
合并的优化方式是按秩合并,这种优化可以忽略,不去使用它。 在数据量非常大,且数据刁钻时,可考虑加上这个优化,否则,优化一 足矣
1、优化方法是创建一个h[]
数组维护每个集合树的深度。
2、在marge
时,将深度小的树作为深度大的的儿子, 最后树的总深度仍是最大的那个深度,这样,在find
时,会提高效率。
3、若两颗树的深度相同,则谁做谁的儿子都可以,只需将其深度加一即可
最终的全优代码如下:
(再次说明:按秩合并在竞赛中基本是用不到的。无需优化)
int p[N], h[N];
int get ( int x )
{
if ( x == p[x] ) return x;
return p[x] = get ( p[x] );
}
void marge ( int x, int y )
{
x = get ( x ), y = get ( y );
if ( h[x] == h[y] )
{
p[x] = y;
h[y] += 1;
} else if ( h[x] < h[y] ) {
p[x] = y;
} else {
p[y] = x;
}
}