并查集可以用来解决一些元素分组问题,分为两种操作:
1、合并:把两个不相交的集合并为一个集合。
2、查询:查询两个元素是否在同一个集合中。
初始化:
class Solution {
private static final int MAX = 500;
private int fa[MAX]; // 存储每个节点的父节点
public void init(int n) {
for(int i = 1; i <= n; i++)
fa[i] = i;
}
}
查询:
public int find(int x) {
if(fa[x] == x)
return x;
else
return find(fa[x]);
}
合并:
public void merge(int i, int j) {
fa[find(i)] = find(j);
}
以上的查询和合并的效率低下,解决方法有路径压缩和按秩合并。
路径压缩(优化查询):
public int find(int x) {
if(x == fa[x])
return x;
else {
fa[x] = find(fa[x]); // 把沿途的每一个节点的父节点都设为根节点
return fa[x];
}
}
路径压缩优化后,并查集并不是始终都是一个菊花图(只有两层的树的俗称),因为路径压缩只在查询时进行,也只压缩一条路径,所以并查集最终的结构仍然可能时比较复杂的。
按秩合并(优化合并):
我们用一个数组rank[]来记录每个根节点对应的树的深度(如果不是根节点,其rank相当于以它作为根节点的子树的深度)。初始化时把所有元素的rank设为1,合并时比较两个根节点,把rank较小者往较大者上合并。这样合并可以避免合并后的树的深度变长,从而不会使查询时间变长。
public void init(int n) {
for(int i = 0; i < n; i++) {
fa[i] = i;
rank[i] = 1; // 初始化每一个节点的rank为1
}
}
public void merge(int i, int j) {
int x = find(i), y = find(j);
if(rank[x] <= rank[y])
fa[x] = y;
else
fa[y] = x;
if(rank[x] == rank[y] && x != y)
rank[y]++; // 如果深度相同且根节点不同,则新的根节点深度+1
}