1.什么是并查集
假设一开始有几个样本,a,b,c,d,e,f 一开始,我们认为他们所在的集合只有自己{a},{b},{c},{d},{e},{f},并查集他是维持着一大堆集合的结构。
并查集提供两个操作:
1.isSameSet 查询两个样本是否在同一个集合 返回Boolean类型 例如 isSameSet(a,e)判断a所在的集合和e所在的集合是不是同一个集合
2.union 将两个样本所在集合的全体,变成一个集合 例如 union(a,e)判断将a所在的集合和e所在的集合合成一个集合。
如果有N个样本,我们调用isSameSet和union很频繁,他能够做到均摊下来单次时间复杂度O(1)。假如说我100万个样本,我们的isSameSet和Union操作巨频繁,频繁到超过100万次,单次平均时间复杂度O(1)。简而言之就是当我们的查询次数大于样本量,那么单次查询时间复杂度O(1)。
2.实现
2.1 什么是代表节点?
从一个集合的任意一个值开始,我们一直往上找,知道不能再往上了,那么这个节点,就是该集合的代表节点。
2.2 isSameSet
我们可以把每个样本都包上一层,让他们有一个指针,指向自己。假如说我判断a e 是否在一个集合,我就找a的代表节点和e的代表节点,看是不是同一个,如果是,则说明在同一个集合,如果不是,则证明不是同一个集合。
2.3 union
如果我们要union a 和 e ,我们先找a的代表节点,再找e的代表节点,然后e的代表节点的指针直接指向a。如果两个节点所在的集合的大小不一样,我们找到相关代表节点后,把小的集合挂在大的集合上面。
2.4 并查集使用HashMap实现
public static class Node<T>{
private T value;
public Node(T value){
this.value = value;
}
}
public static class UnionSetWithHashMap<T>{
// T 是样本
// Node<T> 是包完后那个圈
//对于使用者来说,他是不知道那个圈的,他就人它的样本
//但我们操作的是那个圈,所以需要把他们对应的一一记录下来
private HashMap<T,Node<T>> nodes;
//因为我们没有在node上面添加指针,所以,我们用一张表,来实现指针的效果
//key为带圈的数据, value 为它的父亲
private HashMap<Node<T>,Node<T>> parents;
//我们连接是,需要把小的连接到大的上面,所以我们就需要把代表节点所在集合的大小保存起来
//key为代表节点, value为代表节点所在集合的大小
//只有代表节点会存在这个集合中,如果不是代表节点,就会从这个集合中删除
private HashMap <Node<T>,Integer> sizeMap;
public UnionSetWithHashMap(List<T> value){
parents = new HashMap<>();
sizeMap = new HashMap<>();
for (T t : value) {
Node<T> node = new Node(t);
//初始化,将给的每一个值,都包一层,相当于都加一个圈
nodes.put(t,node);
//初始化时,每个节点的代表节点都是他自己,大小都是1
parents.put(node,node);
sizeMap.put(node,1);
}
}
//寻找到该节点的代表节点
public Node<T> findFather(Node<T> node){
//准备一个栈,用来做路径压缩
Stack<Node<T>> stack = new Stack<>();
while (node != parents.get(node)){
node = parents.get(node);
//将经过的节点全都放入栈中
stack.push(node);
}
//一个一个弹出,把他们的父亲变为 代表节点
/** a 假如说 e 找代表节点,在找的过程中进行路径压缩
* / \ 等找到代表节点后,就会变成
* b c a
* / / \
* d [b,c,d,e]
* / b c d e 均为 a 的孩子,相当于一个多叉树
* e
*/
while (!stack.isEmpty()){
Node<T> pop = stack.pop();
parents.put(pop,node);
}
return node;
}
public Boolean isSameSet(T a,T b){
return findFather(nodes.get(a)) == findFather(nodes.get(b));
}
public void union(T a,T b){
Node<T> aHead = findFather(nodes.get(a));
Node<T> bHead = findFather(nodes.get(b));
//当代表节点不是同一个的时候需要合并
if (aHead != bHead){
Integer aSize = sizeMap.get(aHead);
Integer bSize = sizeMap.get(bHead);
//找出谁的集合大
Node<T> bigHead = aSize >= bSize ? aHead : bHead;
Node<T> smallHead = bigHead == aHead ? bHead : aHead;
//将集合小的父亲设置为大的集合
parents.put(smallHead,bigHead);
//将小的集合从代表节点表移除,大的代表节点大小需要加上小的集合的大小,就是合并后的集合的大小
sizeMap.put(bigHead,aSize + bSize);
sizeMap.remove(smallHead);
}
}
}