并查集
并查集是一种数据结构(不交集数据结构),用于处理一些不交集(Disjoint sets,一系列没有重复元素的集合)的合并及查询问题。
并查集支持如下操作:
- 查询:查询某个元素属于哪个集合,通常是返回集合内的一个“代表元素”。用于判断两个元素是否在同一个集合之中。
- 合并:将把两个不相交的集合合并为一个集合。
并查集操作
初始化
用一个数组 fa[] 来存储每个元素的父节点,一开始,每个元素的父节点设为自己。
find()函数
- 对于某个点x,查询属于哪个集合,返回其所属集合的代表元,以此判断两个元素是否在一个集合内。
- 一层一层访问父节点,直至根节点(父节点是其本身)(代表元)。
join()函数
- 合并两个不相交的集合。
- 先找到两个集合的代表元,然后将前者的父节点设为后者,当然也可以将后者的父节点设为前者,都可。
路径压缩算法一(优化find()函数)
- 把沿途的每个节点的父节点都设为根节点,降低树的高度。
- 第一次查询没有压缩效果,之后才有效。
路径压缩算法二(按秩合并)
- 使用数组rank[],记录每个根节点对应的树的深度(不是根节点,rank为子树深度)
- 初始rank设为1,合并时,(rank)秩较小者往较大者合并
- 如果rank[x]<rank[y], fa[x]=y
- 如果rank[x]==rank[y], 任意指定父节点
- 如果rank[x]>rank[y], x=fa[y]
代码
public static int[] fa; // 存储每个结点的父结点
public static int[] rank; // 树的秩
/**
* 初始化
* fa[]、rank[]
*
* @param n
*/
public static void init(int n) {
for (int i = 0; i < n; i++) {
fa[i] = i; // 初始化,每个结点的父节点设为自己
rank[i] = 1; // 初始化,每个结点构成树,秩为1
}
}
/**
* find()函数,查询x的根节点
*
* @param x
* @return
*/
public static int find(int x) {
/*
// 递归写法
if (x == pre[x]) { // 自己就是代表元,父节点是本身
return x;
} else { // 否则一直找
return find(pre[x]);
}
*/
while (x != fa[x]) { //一层一层访问父节点,直到根节点(代表元)
x = fa[x];
}
return x;
}
/**
* 路径压缩算法一
* 优化find()函数
* 把沿途的每个节点的父节点都设为根节点
*
* @param x
* @return
*/
public static int optimizeFind(int x) {
if (x == fa[x]) {
return x;
} else {
fa[x] = optimizeFind(fa[x]);
return fa[x];
}
}
/**
* join()函数,合并
*
* @param x
* @param y
*/
public static void join(int x, int y) {
// 找到代表元
x = find(x);
y = find(y);
if (x != y) { // 不相等,随便找一个结点当作代表元
fa[x] = y;
}
}
/**
* 路径压缩算法二
* 按秩合并
*
* @param x
* @param y
*/
public static void union(int x, int y) {
// 找到代表元
x = find(x);
y = find(y);
if (x == y) { // 在同一个集合内
return;
}
if (rank[x] > rank[y]) {
fa[y] = x; // x为y的父节点
} else {
if (rank[x] == rank[y]) { // 如果高度相等,y的高度+1
rank[y]++;
}
fa[x] = y; // y为x的父节点
}
}