算法原理
并查集一般有两种方法来保持复杂度不退化,一种是路径压缩,另一种则是按照秩来做启发式合并。
一般情况下我们都是用第一种,压缩路径通过递推找到祖先节点后,在回溯时将它的子孙节点都直接指向祖先,这样以后每次调用Find( )函数找父亲时复杂度就变成了O(1)。但是路径压缩时直接将节点的父亲修改成最终的祖先节点,在破坏原先的树结构的同时,在有些题目中也会损失信息。而不使用压缩路径,直接用暴力并查集又容易超时。
所以我们考虑用启发式合并的方法来保持树的形态,那么如何控制并查集的复杂度呢?
因为并查集是一种树型结构,对于以每个节点为根节点的子树都有一个深度,如果把一棵深度大的树的根节点接在了一棵深度小的树上,因为是直接把根节点接在另一个的根节点上,所以整棵树的深度为那一棵深度大的树的深度加一。而如果把一棵深度小的树的根节点接在了一棵深度大的树上,可直接接上,不影响深度。如果两个数深度一样,则将接完后的树的深度加一即可。所以考虑每次都将深度小的树接在深度大的树上,这就是启发式合并的原理。虽然没有压缩路径,但是按秩合并可以保证树高是O(logn),这样找到树根是O(logn),路径查询也是O(logn)。
实现代码
int fu[maxn]//存放父节点
int deep[maxn];//记录深度
int findx(int x)//启发式合并不压缩路径,保持树结构
{
if(fu[x] == x) return x;
return findx(fu[x]);
}
void join(int x, int y,int k) //按照秩来做启发式合并
{
int fx = findx(x);
int fy = findx(y);
if(fx==fy) return;
if(deep[fx]>deep[fy])//深度小的树接在深度大的树上
swap(fx,fy);
fu[fx] = fy;
if(deep[fx]==d