基础代码实现:
find:路径压缩
union:按秩合并
java
/**
* Quick Union 并查集(带按秩合并和路径压缩优化)
* 两种操作均近似 O(α(n)),α(n) 为反阿克曼函数,效率极高
*/
public class OptimizedUnionFind {
private int[] parent; // parent[i] 表示 i 的父节点
private int[] rank; // rank[i] 表示以 i 为根的树的高度(秩)
private int count; // 连通分量数量
public OptimizedUnionFind(int n) {
count = n;
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i; // 初始时每个节点指向自己
rank[i] = 0; // 树高为 0
}
}
// 查找元素 p 的根节点(带路径压缩)
public int find(int p) {
validate(p);
while (p != parent[p]) {
// 路径压缩:将 p 的父节点直接设为祖父节点
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
// 递归写法(更简洁,但可能栈溢出):
// if (p != parent[p]) parent[p] = find(parent[p]);
// return parent[p];
}
// 判断 p 和 q 是否连通
public boolean connected(int p, int q) {
return find(p) == find(q);
}
// 合并 p 和 q 所在的集合(按秩合并)
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
// 将秩小的树合并到秩大的树上
if (rank[rootP] < rank[rootQ]) {
parent[rootP] = rootQ;
} else if (rank[rootP] > rank[rootQ]) {
parent[rootQ] = rootP;
} else {
parent[rootQ] = rootP;
rank[rootP]++; // 两棵树秩相同,合并后新根秩加 1
}
count--;
}
// 返回当前连通分量个数
public int count() {
return count;
}
private void validate(int p) {
int n = parent.length;
if (p < 0 || p >= n) {
throw new IllegalArgumentException("索引 " + p + " 不合法");
}
}
}
力扣题:
🗺️ LeetCode 并查集题目推荐路线
| 难度 | 题目 | 题号 | 说明 |
|---|---|---|---|
| 入门 | 岛屿数量 | 200 | 最经典的入门题,非常适合第一次接触并查集。 |
| 省份数量 | 547 | 另一种表述的连通分量问题,和"朋友圈"是同一类。 | |
| 冗余连接 | 684 | 判断图中哪条附加的边会导致环的产生。 | |
| 等式方程的可满足性 | 990 | 将并查集应用到非图论的抽象关系判定中。 | |
| 最长连续序列 | 128 | 并查集也可以用来处理数组元素间的连通性问题。 | |
| 进阶 | 被围绕的区域 | 130 | 可以结合DFS/BFS和并查集思维解决。 |
| 账户合并 | 721 | 合并多个账户,可以练习如何将复杂问题抽象为连通性问题。 | |
| 情侣牵手 | 765 | 一个很有意思的题目,考验如何利用并查集解决排列问题。 | |
| 交换字符串中的元素 | 1202 | 结合并查集与排序的字符串处理题。 | |
| 水位上升的泳池中游泳 | 778 | 并查集结合二分查找或排序的经典题目。 | |
| 打砖块 | 803 | 并查集的逆向思维应用(较难)。 | |
| 实战 | 最小生成树 (Kruskal) | 1584, 1135 | Kruskal算法需要依靠并查集来判断是否产生环。 |
| 离线查询 | 处理询问"在某个时间点后,两点是否连通"的动态问题,是并查集的高级应用。 |
自我感悟总结:
并查集的parent数组的索引是元素域,例如990题就是26个字母,parent=new int[26],
对于parent的定义这里的关键是确定parent=new int[n]的n的大小,具体看题目的意思。
rank同理是同一个n,表示节点的秩,也就是树高
count是连通分量,可以一开始定义为count=n;后面每进行一次union就进行count-- ,例如n=5,有五个元素分别为1-5,count=5,union 1,2 2,3 3,4 4,5,union了四次,count=1,也就是连通整体的个数是1,具体可以看200岛屿数量这一题。
而128题并不需要连通分量,要的是每一个分量的元素个数的最大值,此时union可以是int类型的返回值,返回祖父节点的连接的元素个数size[root]。