并查集介绍

171 阅读2分钟

「这是我参与2022首次更文挑战的第36天,活动详情查看:2022首次更文挑战」。

一、并查集的定义

并查集的定义:

  1. 有若干个样本a、b、c、d…类型假设是V

  2. 在并查集中一开始认为每个样本都在单独的集合里

  3. 用户可以在任何时候调用如下两个方法:

    • boolean isSameSet(V x, V y) : 查询样本x和样本y是否属于一个集合

    • void union(V x, V y) : 把x和y各自所在集合的所有样本合并成一个集合

  4. isSameSet和union方法的代价越低越好

二、并查集的特征

  1. 每个节点都有一条往上指的指针
  2. 节点a往上找到的头节点,叫做a所在集合的代表节点
  3. 查询x和y是否属于同一个集合,就是看看找到的代表节点是不是一个
  4. 把x和y各自所在集合的所有点合并成一个集合,只需要小集合的代表点挂在大集合的代表点的下方即可

三、并查集的优化

  1. 节点往上找代表点的过程,把沿途的链变成扁平的
  2. 小集合挂在大集合的下面
  3. 如果方法调用很频繁,那么单次调用的代价为O(1),两个方法都如此

四、并查集的应用

  1. 解决两大块区域的合并问题
  2. 常用在图等领域中

五、并查集的实现

public static class Node<V> {
    V value;

    public Node(V v) {
        value = v;
    }
}

public static class UnionFind<V> {
    public HashMap<V, Node<V>> nodes;
    public HashMap<Node<V>, Node<V>> parents;
    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);
        }
    }

    // 给你一个节点,请你往上到不能再往上,把代表返回
    private Node<V> findFather(Node<V> cur) {
        Stack<Node<V>> path = new Stack<>();
        while (cur != parents.get(cur)) {
            path.push(cur);
            cur = parents.get(cur);
        }
        while (!path.isEmpty()) {
            parents.put(path.pop(), cur);
        }
        return cur;
    }

    // 是否在同一个集合里
    public boolean isSameSet(V a, V b) {
        return findFather(nodes.get(a)) == findFather(nodes.get(b));
    }

    // 合并两个集合
    public void union(V a, V b) {
        Node<V> aHead = findFather(nodes.get(a));
        Node<V> bHead = findFather(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();
    }

}

六、并查集的总结

精髓在于拿代表节点来确定是哪个集合的

并查集3个HashMap搞定

  • 样本对应自己包完的那个圈:HashMap<V,Node<V>> nodes

  • 不想在Node上增加指针,利用HashMap把节点与它的父节点进行映射:HashMap<Node<V>,Node<V>> parents

  • 指定谁挂谁,小挂大:HashMap<<Node<V>,Integer> sizeMap,key:头节点,只留代表节点的记录

为什么小挂大?在并查集链变成扁平化的过程中,减少挂的次数