并查集
在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集的合并和查询问题。
作用
- 检查两个元素是否属于一个集合
- 将两个元素所在的不相交的集合合并
合并操作
- 如图,将每一个元素作为一个集合
- 执行合并操作时,就将每一个元素的指针指向其他集合
例子1:union(3,4);
例子2:union(2,4)
我们合并集合的子元素时,是让一个集合的首节点指向另一个集合的首节点。一般情况下,让集合元素少的首节点指向集合元素多的首节点。还有一种方式是比较树的高度(或者叫秩)来合并。
判断是否是同一个集合
isSameSet(1,2)
节点1的指针指向自己说明集合首节点就是他自己,而节点2指向3,3执行自己,那么节点2的集合的首节点就是节点3,如果首节点相同,则属于同一个节点,否则不属于同一个节点。
代码实现
public class UnionFind<T> {
private HashMap<T,T> fatherMap;//节点指向父节点的映射
private HashMap<T,Integer> sizeMap;//首节点指向集合的大小
public UnionFind() {
fatherMap = new HashMap<>();
sizeMap = new HashMap<>();
}
public void makeSet(List<T> data){
fatherMap.clear();
sizeMap.clear();
for (T t : data){
fatherMap.put(t,t);
sizeMap.put(t,1);
}
}
//获取首节点,判断依据是当前节点的首节点是否为本身
private T find(T t1){
T p = t1;
T head = fatherMap.get(p);
while(head != p){
p = head;
head = fatherMap.get(p);
}
return head;
}
//如果首节点相同,就认为属于同一个集合
public boolean isSameSet(T t1,T t2){
return find(t1) == find(t2);
}
//合并集合
public void union(T t1,T t2){
T head1 = find(t1);
T head2 = find(t2);
if(head1 != head2){
int size1 = sizeMap.get(head1);
int size2 = sizeMap.get(head2);
if(size1 >= size2){
fatherMap.put(head2,head1);
sizeMap.put(head1,size1+size2);
}else {
fatherMap.put(head1,head2);
sizeMap.put(head2,size1+size2);
}
}
}
}
路径压缩
如图,在最差情况下并查集的一次查找的时间复杂度为O(log(n))
此时我们可以在find操作时,比如find(4)时可以将节点4直接指向首节点1
结果如下:
更改代码如下
public class UnionFind<T> {
private Stack<T> stack;//用来保存查到的节点
private T find(T t1){
T p = t1;
T head = fatherMap.get(p);
while(head != p){
stack.push(p);//存储查询到的节点
p = head;
head = fatherMap.get(p);
}
for (T t : stack){//遍历,将该节点直接指向首节点
fatherMap.put(t,head);
}
stack.clear();
return head;
}
...
}