并查集算法中,为了避免发生树的退化,利用了两个重要的优化思想:
优化一:
对于每棵树,记录这棵树的高度(rank)。
合并时如果两棵树的rank不同,那么从rank小的向rank大的连边。
优化二:
路径压缩——对于每个节点,一旦向上走了一次节点,就把经过的这个点携带着一起指向最后的根节点。(路径压缩时,为了简单起见,即使树的高度发生了变化,我们也不修改rank的值)。
/*
* 在这里,“树”与“集合”同义,“根节点”与“祖先”同义
*/
import java.util.*;
public class Main {
static Scanner sc = new Scanner(System.in);
static int n,m;//节点数,边数
static int[] f;//根
static int[] rank;//树的高度
static void init(){//初始化
n=sc.nextInt();
m=sc.nextInt();
f=new int[n];
rank=new int[n];
for(int i=0;i<n;i++){
f[i]=i;
rank[i]=1;
}
}
static int find(int x){//递归得到x的根节点
if(f[x]==x)
return x;
else
return f[x]=find(f[x]);//路径压缩:函数返回时,顺带把路上遇到的节点的根节点改为最后找到的根节点
}
static void merge(int x,int y){//合并x和y所属的集合
x=find(x);
y=find(y);
if(x!=y){//不属于同一集合,合并
if(rank[x]<rank[y])
f[x]=y;//矮树向高树合并,(高树的)高度不变
else{
f[y]=x;//矮树向高树合并,(高树的)高度不变
if(rank[x]==rank[y])//合并前两棵树高度相等,合并后高度一定加1
rank[x]++;
}
}
}
static boolean same(int x,int y){//判断x和y是否属于同一个集合
return find(x)==find(y);
}
public static void main(String[] args) {
init();
for(int i=0;i<m;i++)
merge(sc.nextInt(),sc.nextInt());
}
}
时间复杂度:
加入了这两个优化后的并查集效率非常高。对n个元素的并查集进行一次操作的复杂度是O(α(n))。在这里,α(n)是阿克曼函数的反函数,比O(log(n))还要快。
不过,这是“均摊复杂度”。也就是说,并不是每一次操作都满足这个复杂度,而是多次操作后平均每一次操作的复杂度是O(α(n))的意思。