并查集:支持两个集合合并,两个是否一个集合。让两个操作几乎都是O(1)<br /> 如果使用链表 合并是O(1) 查询是否一个集合就不是了,使用hash表 查询是O(1),合并就不是了。<br />使用往上值的图的方式:<br /> 看两个是否一个集合:指针一直往上指,指到不能再指了,看是否同一个代表元素,是就是同一集合,否则不是。<br /> 两个集合合并:当两个不是集合的情况都是自己指自己,改变某个顶指针,指向另外一个。 <br /> 当两个是集合时候,数量少的顶部连向数量多的顶部。<br /> 关键是往上指到不能再指了,这个过程如果链条过长会达到瓶颈,所以当指到不能再指的时候将沿途的节点都指向最上面的。
// 样本会进来包一层,叫做元素
public static class Node<V> {
V value;
public Node(V v) {
value = v;
}
}
// 并查集结构
public static class UnionFind<V> {
// 包一层 a对应a圈
public HashMap<V, Node<V>> nodes;
// key 某个元素,value指该元素的父 。 小弟 大哥
public HashMap<Node<V>, Node<V>> parents;
// key 某个集合的代表元素 ,value 该集合的大小,包含一个顶点和有多少小弟
public HashMap<Node<V>, Integer> sizeMap;
public UnionFind(List<V> values) {
nodes = new HashMap<>();
parents = new HashMap<>();
sizeMap = new HashMap<>();
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> findHead(Node<V> cur) {
Stack<Node<V>> path = new Stack<>();
// 不是顶点的时候
while (cur != parents.get(cur)) {
// 沿途节点加入栈
path.push(cur);
// 获得最顶点
cur = parents.get(cur);
}
while (!path.isEmpty()) {
// 将这条链上的father全都改成cur,扁平化
parents.put(path.pop(), cur);
}
return cur;
}
// 是否一个集合
public boolean isSameSet(V a, V b) {
// 是否初始化
if(nodes.containsKey(a)&& nodes.containsKey(b)){
// 是不是同一个顶点
return findHead(nodes.get(a)) == findHead(nodes.get(b));
}
return false;
}
// 合并
public void union(V a, V b) {
if(nodes.containsKey(a)&& nodes.containsKey(b)){
// 找到各自顶点
Node<V> aHead = findHead(nodes.get(a));
Node<V> bHead = findHead(nodes.get(b));
if (aHead != bHead) {
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);
// 更改合并完大小
sizeMap.put(big, aSetSize + bSetSize);
// 删除之前的顶点,因为合并了。
sizeMap.remove(small);
}
}
}
public int sets() {
return sizeMap.size();
}
}