并查集:主要用于解决一些不相交集合的合并及查询问题。查询两个元素是否在同一个集合中,以及合并两个不相交集合。当然我们可以用链表等其他数据结构来实现,担当数据量特别大的时候,使用链表是非常耗时的,采用并查集结构可以大大提高合并及查询的效率。
实现原理:并查集也是一种树的结构,通过将两个元素的根节点合并从而来实现两个元素所在集合的合并,所以通过判断两个元素的根节点是否相同,就能确定这两个元素是否在同一个集合中。初始化并查集时,我们将每一个元素的根节点都指向自己。
根节点,下文成为 “ 代表节点 ”
Java详细实现:
package unionfind;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
/**
* @program: algorithm
* @description: UnionFindSet
* @author: LKKKK
* @create: 2023-07-12 12:13
**/
public class UnionFindSet2<T> {
//并查集中的元素
Map<T,Element2<T>> elementMap = new HashMap<>();
//每个element对应的父节点
Map<Element2<T>,Element2<T>> fatherMap = new HashMap();
//以代表节点为根节点的树的节点个数
Map<Element2<T>,Integer> sizeMap = new HashMap();
public static class Element2<T>{
T value;
public Element2(T value){
value = value;
}
}
//构造方法中对并查集进行初始化
public UnionFindSet2(List<T> list){
for (T t : list) {
Element2<T> tElement2 = new Element2<>(t);
elementMap.put(t,tElement2);
//初始化时,每个元素的父节点就是自己
fatherMap.put(tElement2,tElement2);
sizeMap.put(tElement2,1);
}
}
//找元素的代表节点
public Element2<T> findHead(Element2 e){
Stack<Element2> path = new Stack<>();
//找代表节点
while(e != fatherMap.get(e)){
//将在找代表节点的过程中途径的节点存到栈中
path.push(e);
//更新element继续找
e = fatherMap.get(e);
}
//做优化(将并查集结构扁平化,避免极端情况下并查集结构退化为链表结构),将path中的节点的父节点都指向找到的代表节点,这样在以后找的时候就不用遍历fatherMap,从fatherMap中一步直接可以拿到
while(!path.isEmpty()){
fatherMap.put(path.pop(),e);
}
return e;
}
public boolean isSameSet(T t1,T t2){
//判断t1,t2是不是在并查集中
if(elementMap.containsKey(t1) && elementMap.containsKey(t2)){
//在的话,判断他们的代表节点是不是一样
return findHead(elementMap.get(t1)) == findHead(elementMap.get(t2));
}
//不在并查集中的话返回false
return false;
}
public void union(T t1,T t2){
//判断是否存在于并查集中
if(elementMap.containsKey(t1) && elementMap.containsKey(t2)){
//不在一个集合中的话就进行union(他们的代表节点是否相同)
Element2<T> aHead = findHead(elementMap.get(t1));
Element2<T> bHead = findHead(elementMap.get(t2));
if(aHead != bHead){
//小的合并到大的里面
Element2<T> big = sizeMap.get(aHead) > sizeMap.get(bHead) ? aHead : bHead;
Element2<T> small = aHead == big ? bHead : aHead;
//合并
//将small的代表节点设置为big
fatherMap.put(small,big);
//更新sizeMap中big的大小
sizeMap.put(big,sizeMap.get(small) + sizeMap.get(big));
//把small节点从sizeMap中删除,因为small已经不是代表节点了
sizeMap.remove(small);
}
}
}
}