一、启发式合并的算法原理
一听这名字,感觉好高大上,实际上很简单。
由于路径压缩在有些题目会损失海量的信息,用暴力并查集又要超时,所以就出现了启发式合并算法。
之前讲过并查集的大部分时间都浪费在了find()函数上,于是就对find()函数进行了优化,其实启发式合并算法可以使find()函数的时间复杂度控制在O(logn)左右。
并查集是一种树型的数据结构,而树也有它的深度,如果我们把一棵深度大的树的根节点接在了一棵深度小的树上,那么整棵树的深度为那一棵深度大的树的深度+1,如果我们把一棵深度小的树的根节点接在了一棵深度大的树上,则整棵树的深度为max(深度小的树的深度+1,深度大的树的深度)。这就是启发式合并的原理。
如果感觉很难理解,看下面的图就知道了。
1、有两棵树,一棵高度为3,一棵高度为5。
2、如果是普通的合并,就会造成高度为6的树。
3、如果是启发式合并,最后的树的的高度为5。
好了,启发式合并的原理讲得差不多了,可以发代码了,记住height[]数组初始化为1。
二、启发式合并的代码
void qfsunion(int x,int y)
{
int a=find(x);
int b=find(y);
if(height[a]>height[b])
fa[b]=a;
else if(height[a]<height[b])
fa[a]=b;
else{
fa[a]=b;
height[b]++;
}
}
配套一个find()函数:
int find(x)
{
while(fa[x]!=0)
x=fa[x];
return x;
}
三、启发式合并与路径压缩之间的问题
这时有人会问了:为什么find()函数不用路径压缩呢?
原因很简单,因为有了路径压缩,启发式合并算法就没有保护数据的效果了。
可以看出,启发式合并+路径压缩并不是最好的选择,而且路径压缩在主动改变树的高度,但是height数组的值不能同步改变,有可能会让启发式合并出错。
四、一些补充
由于一些历史原因,本文所讲的启发式合并,现在在严格意义上称为按秩合并,而现在并查集的启发式合并算法是根据两棵树的节点数目进行合并,具体内容详见OI Wiki