Union Find 算法介绍
Union Find 是一个很强大的算法,能解决动态联通性的一类问题。主要操作是find 和union。通过指定的两个点之间的关系合并这两个点,使他们属于一个组织。这样随着不断输入边,相互独立的组织会越来越少。整个图的连通性越来越强。
Find()操作也就是查找给定的点属于哪个组织,union()操作是将两个点合并到一个组织,包括这两个点所属组织的所有点都合并在一起。
底层数据结构:用id[n] 记录节点所属的组织,这样 find()操作很简单只要返回 id[i]即可,但是这样会带来缺陷,在union的时候要将所有属于i的点的id[i]全都设置成id[j],这需要扫描整个id[n]。怎么解决呢,我们可以把id[n]看做树状结构,id[i]表示i的父节点,id[id[i]]表示id[i]的父节点,当到达根节点时id[i] = i,这样在find()的时候只要找到 id[i] = i返回i即可,合并的时候只要将i的根节点父指针指向j节点即可。但是,我们进一步思考,由于在合并两个树的时候,我们没有考虑该将哪颗树挂到哪棵树上,这样势必可能会使合并的时候大树挂在小树上从而使树的高度增加,这样对后来的查找性能会产生很大的影响。如何解决呢?我们可以考虑给树设置权重,分配rank[n]来表示权重,在union的时候,将rank小的节点挂到rank大的结点上。这样find性能得到了改进。能不能更进一步改进呢?还是可以的,我们可以考虑压缩路径,在find()的时候,将i节点的父指针指向它的爷爷节点,这样在find的过程中,整个树的高度越来越短,从而让find()的效率接近O(1)。下面是完整的Union Find代码:
class UF{
public int count;
public int[] id;
public int[] rank;
public UF(int n){
count = n;
id = new int[n];
rank = new int[n];
for(int i = 0; i < n; i++){
id[i] = i;
rank[i] = 1;
}
}
public int find(int p){
if(id[p] != p) id[p] = find(id[p]);
return id[p];
}
public void union(int p, int q){
int pp = find(p);
int qq = find(q);
if(pp == qq) return;
if(rank[pp] > rank[qq]){
id[qq] = pp;
rank[pp] += rank[qq];
}
else{
id[pp] = qq;
rank[qq] += rank[pp];
}
count--;
}
}