并查集的一个简单实现

引言:并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题,例如kruskal算法.

根据其原理我们简单实现一个.

并查集主要有两个操作:合并,查找


我们可以回忆下链表,假设某些元素都在同一个链表,我们则认为这些元素是同一个集合


如a:{1,2,3},集合中元素1 和 2查找的结果是同一个集合为true,然后我们关联{1,4},那么元素3和4的查找结果也是true


实现思路:
借用链表的思路,我们可以在初始化的时候,将所有不同元素都设置为root根节点,然后合并的时候,将一个root的parent设置为另一个root
这样我们可以根据两个元素的root是不是同一个,来判断两个元素的关联性

 

public class UnionFind<V> {
    // 每个Node的parent映射, root的parent是自己
    private Map<Node<V>, Node<V>> parentMap;
    // v 和 Node 的映射
    private Map<V, Node<V>> map;
    // root 的子节点的数量,只有root才有
    private Map<Node<V>, Integer> rootSize;

    /**
     * @param list root的集合
     */
    public UnionFind(List<V> list) {
        parentMap = new HashMap<>(list.size());
        map = new HashMap<>(list.size());
        rootSize = new HashMap<>(list.size());
        for (V v : list) {
            Node<V> node = new Node<>(v);
            map.put(v, node);
            parentMap.put(node, node);
            rootSize.put(node, 1);
        }
    }

    /**
     * 判断两个数据源是否是同一个集合
     */
    public boolean isSameSet(V a, V b) {
        return findRoot(a) == findRoot(b);
    }

    private Node<V> findRoot(V a) {
        Node<V> aNode = map.get(a);
        Deque<Node<V>> st = new ArrayDeque<>();
        while (aNode != parentMap.get(aNode)) {
            st.push(aNode);
            aNode = parentMap.get(aNode);
        }
        // 优化,将当前所有的的子节点的parent都直接设置为root
        while (!st.isEmpty()) {
            Node<V> node = st.pop();
            parentMap.put(node, aNode);
        }
        return aNode;
    }

    /*
     * 关联两个数据源
     * */
    public void union(V a, V b) {
        Node<V> aRoot = findRoot(a);
        Node<V> bRoot = findRoot(b);
        // 同一个集合直接返回
        if (aRoot == bRoot) {
            return;
        }
        // 将小集合的root指向大节点的root
        if (rootSize.get(aRoot) >= rootSize.get(bRoot)) {
            // 将bRoot的parent设置为aRoot;
            parentMap.put(bRoot, aRoot);
            // 更新rootSize
            rootSize.put(aRoot, rootSize.get(bRoot) + rootSize.get(aRoot));
            // 移除bRoot,因为此时已不再是根节点
            rootSize.remove(bRoot);
        } else {
            parentMap.put(aRoot, bRoot);
            rootSize.put(bRoot, rootSize.get(bRoot) + rootSize.get(aRoot));
            rootSize.remove(aRoot);
        }

    }

    static class Node<V> {
        V value;

        public Node(V value) {
            this.value = value;
        }
    }
}

写一个简单的测试:

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
        UnionFind<Integer> uf = new UnionFind<>(list);
        System.out.println(uf.isSameSet(1,2));// false
        uf.union(1,2);
        uf.union(1,3);
        System.out.println(uf.isSameSet(2,3));// true

    }

感兴趣的可以取思考下操作的时间复杂度,注意上面的findRoot方法的优化操作

posted @ 2022-06-06 21:58  junlancer  阅读(36)  评论(0编辑  收藏  举报