数据结构和算法(十一)并查集

240 阅读2分钟

并查集

在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集的合并和查询问题。

作用

  1. 检查两个元素是否属于一个集合
  2. 将两个元素所在的不相交的集合合并

合并操作

  1. 如图,将每一个元素作为一个集合

并查集1.png

  1. 执行合并操作时,就将每一个元素的指针指向其他集合

例子1:union(3,4);

并查集2.png

例子2:union(2,4)

并查集3.png

我们合并集合的子元素时,是让一个集合的首节点指向另一个集合的首节点。一般情况下,让集合元素少的首节点指向集合元素多的首节点。还有一种方式是比较树的高度(或者叫秩)来合并。

判断是否是同一个集合

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);
            }
        }
    }
}

路径压缩

并查集5.png

如图,在最差情况下并查集的一次查找的时间复杂度为O(log(n))

此时我们可以在find操作时,比如find(4)时可以将节点4直接指向首节点1

并查集6.png

结果如下:

并查集7.png

更改代码如下

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;
    }

   ...
}

参考