并查集

《数据结构与算法》

本文来源于liuyubobobo的“算法与数据结构--综合提升篇”视频教程

并查集是一种树形的数据结构,可用于判断数据是否相连接。

例如在下图中任意取两个点,怎么判断这两个点是否连接在一起呢?

上图中的这个问题太复杂,举个简单的例子来练手。

使用quick find算法实现并查集

public class QuickFind {

    private int[] id;

    /**
     * 初始化数据,数据是下标,元素值也是下标,表示初始化时没有相连接的数据
     */
    public QuickFind(int size){
        id = new int[size];
        for (int i=0; i<size; i++){
            id[i] = i;
        }
    }

    public int getSize(){
        return id.length;
    }

    private int find(int p){
        if (p<0 || p>=id.length){
            throw new RuntimeException("越界");
        }
        return id[p];
    }

    // 判断元素id[p]是否混合id[q]想连接
    public boolean isConnected(int p, int q){
        return find(p) == find(q);
    }

    // 连接接id[p]、id[q]
    public void unionElements(int p, int q){
        int pID = find(p);
        int qID = find(q);

        if (pID == qID){
            return;
        }

        // 将id[p]的值改成id[q]的值,则id[p]、id[q]就可以表示为相连接了
        for (int i=0; i<id.length; i++){
            if (id[i]==pID){
                id[i] = qID;
            }
        }
    }

    public static void main(String[] args) {
        QuickFind uf1 = new QuickFind(10);
        System.out.println(uf1.find(8));
        System.out.println(uf1.find(1));
        System.out.println(uf1.isConnected(1, 8));

        uf1.unionElements(1, 8);

        System.out.println(uf1.find(8));
        System.out.println(uf1.find(1));
        uf1.isConnected(1, 8);
        System.out.println(uf1.isConnected(1, 8));
    }

}

quick find算法的特点是查找快、合并慢。

unionElements(int p, int q)方法的时间复杂度是O(n)级别的,find(int p)、isConnected(int p, int q)方法的时间复杂度都是O(1)级别的。

为了平衡查找、合并的效率,实现并查集更加常用的算法是quick union。

下面用一个数组讲解quick union合并流程

public class QuickUnion {

    // 指向的父亲节点数组
    private int[] parent;
    // sz[i]表示以i为根的集合元素个数
    private int[] sz;

    public QuickUnion(int size){
        parent = new int[size];
        sz = new int[size];

        for (int i=0; i<size; i++){
            parent[i] = i;
            sz[i] = 1;
        }
    }

    public int getSize(){
        return parent.length;
    }

    // 返回的是根节点
    private int find(int p){
        if (p<0 || p>=parent.length){
            throw new RuntimeException("越界");
        }

        /**
         * 如果p的父亲节点不是自己,则继续寻找上一级节点,直至节点的父亲节点也是自己
         * 即返回根节点
         */
        while (p != parent[p]){
            p=parent[p];
        }
        return p;
    }

    public boolean isConnected(int p, int q){
        return find(p) == find(q);
    }

    public void unionElements(int p, int q){
        int pRoot = find(p);
        int qRoot = find(q);

        if (pRoot == qRoot){
            return;
        }

        if (sz[pRoot] < sz[qRoot]){
            // sz[pRoot]个数少
            // pRoot作为子节点指向qRoot
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        } else {
            // sz[qRoot] <= sz[pRoot]
            // qRoot作为子节点指向pRoot
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[pRoot];
        }
    }


    public static void main(String[] args) {

        QuickUnion uf1 = new QuickUnion(10);
        System.out.println(uf1.find(8));
        System.out.println(uf1.find(1));
        System.out.println(uf1.isConnected(1, 8));

        uf1.unionElements(1, 8);

        System.out.println(uf1.find(8));
        System.out.println(uf1.find(1));
        uf1.isConnected(1, 8);
        System.out.println(uf1.isConnected(1, 8));

    }
}

上面的代码中sz记录的是相连接节点集合的个数,这种方式称为基于size的优化。

基于size的优化在某些情况下存在bug,比方说下面的情况。

基于rank优化的代码

public static class QuickUnionRank {

    // 指向的父亲节点数组
    private int[] parent;
    // rank[i]表示以i为根的树高度
    private int[] rank;

    public QuickUnionRank(int size){
        parent = new int[size];
        rank = new int[size];

        for (int i=0; i<size; i++){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    public int getSize(){
        return parent.length;
    }

    // 返回的是根节点
    private int find(int p){
        if (p<0 || p>=parent.length){
            throw new RuntimeException("越界");
        }

        /**
         * 如果p的父亲节点不是自己,则继续寻找上一级节点,直至节点的父亲节点也是自己
         * 即返回根节点
         */
        while (p != parent[p]){
            p=parent[p];
        }
        return p;
    }

    public boolean isConnected(int p, int q){
        return find(p) == find(q);
    }

    public void unionElements(int p, int q){
        int pRoot = find(p);
        int qRoot = find(q);

        if (pRoot == qRoot){
            return;
        }


        if (rank[pRoot] < rank[qRoot]){
            // pRoot高度 小于 qRoot高度
            // pRoot指向qRoot
            parent[pRoot] = qRoot;
        } else if (rank[qRoot] < rank[pRoot]){
            // qRoot高度 小于 pRoot高度
            // qRoot指向pRoot
            parent[qRoot] = pRoot;
        } else {
            parent[pRoot] = qRoot;
        }

    }


    public static void main(String[] args) {

        QuickUnionRank uf1 = new QuickUnionRank(10);
        System.out.println(uf1.find(8));
        System.out.println(uf1.find(1));
        System.out.println(uf1.isConnected(1, 8));

        uf1.unionElements(1, 8);

        System.out.println(uf1.find(8));
        System.out.println(uf1.find(1));
        uf1.isConnected(1, 8);
        System.out.println(uf1.isConnected(1, 8));

    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值