并查集被很多OIer认为是最简洁而优雅的数据结构之一,主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操作:
合并(Union):把两个不相交的集合合并为一个集合。
查询(Find):查询两个元素是否在同一个集合中。
关键点
1: 合并时,小集合的根结点指向大集合的根结点
2:查找时,进行打平,路径压缩
参考算法导论第23章,并查集可以实现在数据量非常庞大时,查询一次的时间复杂度低于6,可以认为查询是一个O(1)的操作。
基本实现
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
public class UnionFind<V> {
public HashMap<V, V> fatherMap;
public HashMap<V, Integer> sizeMap;
public UnionFind(List<V> list) {
fatherMap = new HashMap<>();
sizeMap = new HashMap<>();
for (V a :
list) {
fatherMap.put(a, a);
sizeMap.put(a, 1);
}
}
public boolean isSameSet(V a, V b) {
if (fatherMap.keySet().contains(a) && fatherMap.keySet().contains(b)) {
findHead(a).equals(findHead(b));
}
return false;
}
private V findHead(V a) {
Stack<V> path = new Stack<>();
while (a != fatherMap.get(a)) {
path.push(a);
a = fatherMap.get(a);
}
while (!path.isEmpty()) {
fatherMap.put(path.pop(), a); // 路径打平
}
return a;
}
public void union(V a, V b) {
if (fatherMap.keySet().contains(a) && fatherMap.keySet().contains(b)) {
V aF = fatherMap.get(a), bF = fatherMap.get(b);
if (!aF.equals(bF)) {
V bigBlock = sizeMap.get(aF) > sizeMap.get(bF) ? aF : bF;
V smallBlock = bigBlock == aF ? bF : aF;
fatherMap.put(smallBlock, bigBlock); // 小的向大的集合合并
sizeMap.put(bigBlock, sizeMap.get(aF) + sizeMap.get(bF));
sizeMap.put(smallBlock, 1); // 下一次执行findHead时必然成为叶子节点
}
}
}
}
特别的,如果元素个数有限,我们可以用二维数组代替map结构,可以压缩空间使用率