12-并查集
12. 并查集
12.1 并查集
1. 题目
并查集提供两个功能:
1. 看两个元素是否是同一个集合
1. 将两个元素所在集合的全体合
1. 均摊下来(比如一百万的数据,有一亿查询)是O(1)
2. 思路
判断集合:每个节点中加入一个指针,初始都指向自己。这个指针一直往上找,找到最上面的就是一个集合的代表节点,判断两个节点的代表结点是否一样,就可以知道这两个节点是不是同一个集合里的了。
合并:e的代表结点指向a,那就代表二者合并。如果有个c想加入,a长度为3,c长度为5,小的挂载大的上。
关键:找到代表节点(当前节点一直向上的根)
3. 代码
节点:
public static class Node<V> {
V value;
public Node(V v) {
value = v;
}
}
所需的结构:
// 将用户给的节点包装成自己的Node
public HashMap<V, Node<V>> nodes;
// 记录当前节点的父节点所在,Node中加指针也行无所谓
public HashMap<Node<V>, Node<V>> parents;
// 记录代表节点的size,只有代表结点在里面存,
// 合并后不再是代表结点的节点会被移除表
public HashMap<Node<V>, Integer> sizeMap;
初始化:
public UnionFind(List<V> values) {
nodes = new HashMap<>();
parents = new HashMap<>();
sizeMap = new HashMap<>();
// 节点初始化,每个节点都指向自己(parents<自己,自己>)
for (V cur : values) {
Node<V> node = new Node<>(cur);
nodes.put(cur, node);
parents.put(node, node);
sizeMap.put(node, 1);
}
}
代表节点:
// 找到代表节点
public Node<V> findFather(Node<V> cur) {
// 优化:为了让我们的并查集扁平化
// 当查询的时候,将路径上的所有值都保存下来
// 找到代表节点的时候,就把这些值的parents都指向代表结点
// 这样就保证查询O(1)
Stack<Node<V>> path = new Stack<>();
// 路径压栈,直到找到一个父节点为自己的节点(代表结点)
while (cur != parents.get(cur)) {
path.push(cur);
cur = parents.get(cur);
}
// 走到头之后,cur就是代表节点
// 这里的循环开始是put(代表节点,代表节点)无伤大雅
while (!path.isEmpty()) {
parents.put(path.pop(), cur);
}
return cur;
}
判断是否为同一个节点:
// 判断两个节点是否为同一个代表节点即可
public boolean isSameSet(V a, V b) {
return findFather(nodes.get(a)) == findFather(nodes.get(b));
}
合并节点:
// 合并节点
public void union(V a, V b) {
// 1. 找到代表节点
Node<V> aHead = findFather(nodes.get(a));
Node<V> bHead = findFather(nodes.get(b));
if (aHead != bHead) {
// 获取size
int aSetSize = sizeMap.get(aHead);
int bSetSize = sizeMap.get(bHead);
// 确定谁大谁小
Node<V> big = aSetSize >= bSetSize ? aHead : bHead;
Node<V> small = big == aHead ? bHead : aHead;
// 小的加入到大的上
parents.put(small, big);
// 更新size,并且删除小的size
sizeMap.put(big, aSetSize + bSetSize);
sizeMap.remove(small);
}
}