目录
数据结构
方法实现
优化技巧
实现一个基于哈希表的并查集(Disjoint Set Union, DSU)数据结构,使用了路径压缩和按秩合并的优化技巧。下面我将详细解释这个实现的原理和各个部分的功能。
public interface IDisjointSet {
void add(E x);
void union(E x, E y);
boolean isConnected(E x, E y);
int getSize(E x);
E find(E x);
}
1. items:一个`Map
2. count:一个`Map
3. rank:一个`Map
1. add(E e):
- 如果元素`e`不存在于`items`中,则将其添加进去,并设置其父节点为自身,集合大小为1,秩为0。
2. find(E x):
- 找到元素`x`所属集合的根节点,并进行路径压缩。
- 路径压缩:在查找根节点的过程中,将经过的节点直接连接到根节点,减少树的高度,加快后续查找速度。
- 具体步骤:
- 循环找到根节点`root`,即`items.get(root) == root`。
- 然后进行路径压缩,将从`x`到`root`路径上的所有节点直接连接到`root`。
3. union(E x, E y):
- 将元素`x`和`y`所在的集合合并。
- 首先找到`x`和`y`的根节点`rootX`和`rootY`。
- 如果`rootX`和`rootY`不同,则进行合并:
- 按秩合并:比较两个集合的秩,将秩较小的集合合并到秩较大的集合上。
- 如果秩相等,可以任意选择一个集合合并到另一个上,并将合并后的集合秩加1。
4. getSize(E x):
- 返回元素`x`所在集合的大小,通过找到`x`的根节点,然后从`count`中获取集合大小。
5. isConnected(E x, E y):
- 判断元素`x`和`y`是否在同一个集合中,通过比较它们的根节点是否相同。
- 路径压缩:在`find`操作中,通过将路径上的所有节点直接连接到根节点,大大减少了树的高度,使得后续的`find`操作非常快,几乎为常数时间。
- 按秩合并:在`union`操作中,通过维护每个集合的秩,并在合并时将秩较小的集合合并到秩较大的集合上,保证了树的高度尽可能小,从而保持了`find`操作的高效性。
这个并查集实现使用了哈希表来存储元素及其父节点、集合大小和秩,适用于元素数量不确定或者元素是对象的场景。通过路径压缩和按秩合并,保证了高效的查询和合并操作,时间复杂度接近O(1)。
完整代码:
public class MyDisjointSet implements IDisjointSet {
private Map items; // 存储每个元素的父节点
private Map count; // 存储每个集合的大小
private Map rank; // 排序,即树的高度(用于优化合并操作)
// 构造函数
public MyDisjointSet() {
items = new HashMap<>();
count = new HashMap<>();
rank = new HashMap<>();
}
// 添加元素
public void add(E e) {
if (!items.containsKey(e)) {
items.put(e, e);
count.put(e, 1);
rank.put(e, 0);
}
}
// 查找元素 x 所属的集合,并进行路径压缩
@Override
public E find(E x) {
E root = x;
while (items.get(root) != root) {
root = items.get(root);
}
// 路径压缩
E current = x;
while (current != root) {
E parent = items.get(current);
items.put(current, root);
current = parent;
}
return root;
}
// 合并元素 x 和 y 所在的集合
@Override
public void union(E x, E y) {
E rootX = find(x);
E rootY = find(y);
if (!rootX.equals(rootY)) {
// 按秩合并
int rankX = rank.get(rootX);
int rankY = rank.get(rootY);
if (rankX > rankY) {
items.put(rootY, rootX);
count.put(rootX, count.get(rootX) + count.get(rootY));
} else {
items.put(rootX, rootY);
count.put(rootY, count.get(rootY) + count.get(rootX));
if (rankX == rankY) {
rank.put(rootY, rankY + 1);
}
}
}
}
// 获取元素 x 所属集合的大小
@Override
public int getSize(E x) {
E root = find(x);
return count.get(root);
}
// 判断元素 x 和 y 是否属于同一个集合
@Override
public boolean isConnected(E x, E y) {
return find(x).equals(find(y));
}
}
测试:
public class Test {
public static void main(String[] args) {
IDisjointSet disjointSet = new MyDisjointSet<>();
disjointSet.add(1);
disjointSet.add(2);
disjointSet.add(3);
disjointSet.add(4);
disjointSet.add(5);
disjointSet.add(6);
disjointSet.add(7);
disjointSet.add(8);
disjointSet.add(9);
disjointSet.add(10);
disjointSet.union(1, 2);
disjointSet.union(2, 3);
disjointSet.union(4, 5);
disjointSet.union(6, 7);
disjointSet.union(5, 6);
disjointSet.union(3, 7);
System.out.println("connect:");
System.out.println(disjointSet.isConnected(1, 3));
System.out.println(disjointSet.isConnected(1, 5));
System.out.println(disjointSet.isConnected(4, 6));
System.out.println(disjointSet.isConnected(4, 7));
System.out.println(disjointSet.isConnected(1, 7));
System.out.println("size:");
System.out.println(disjointSet.getSize(1));
System.out.println(disjointSet.getSize(2));
System.out.println(disjointSet.getSize(3));
System.out.println(disjointSet.getSize(4));
System.out.println(disjointSet.getSize(5));
System.out.println(disjointSet.getSize(6));
System.out.println(disjointSet.getSize(7));
System.out.println("find:");
System.out.println(disjointSet.find(1));
System.out.println(disjointSet.find(2));
System.out.println(disjointSet.find(3));
System.out.println(disjointSet.find(4));
System.out.println(disjointSet.find(5));
System.out.println(disjointSet.find(6));
System.out.println(disjointSet.find(7));
System.out.println(disjointSet.find(8));
System.out.println(disjointSet.find(9));
}
}
结果:
connect:
true
true
true
true
true
size:
7
7
7
7
7
7
7
find:
7
7
7
7
7
7
7
8
9