第三十一篇 玩转数据结构——并查集(Union Find)

Posted xuezou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第三十一篇 玩转数据结构——并查集(Union Find)相关的知识,希望对你有一定的参考价值。

 

 

 

1.. 并查集的应用场景
  • 查看"网络"中节点的连接状态,这里的网络是广义上的网络
  • 数学中的集合类的实现
 
2.. 并查集所支持的操作
  • 对于一组数据,并查集主要支持两种操作:合并两个数据、判断两个数据是否属于同一集合(两个数据是否连接)
 
3.. 定义并查集的接口
  • 并查集的接口业务逻辑如下:
  • public interface UF {
    
        int getSize();
    
        boolean isConnected(int p, int q);
    
        void unionElements(int p, int q);
    
    }

     

4.. 实现并查集
  • 第一版并查集Quick Find,业务逻辑如下:
  • public class UnionFind1 implements UF {
    
        private int[] id;
    
        public UnionFind1(int size) {
    
            id = new int[size];
            for (int i = 0; i < id.length; i++) {
                id[i] = i;
            }
        }
    
        // 实现getSize方法
        @Override
        public int getSize() {
            return id.length;
        }
    
        private int find(int p) {
    
            if (p < 0 || p >= id.length) {
                throw new IllegalArgumentException("p is out of bound.");
            }
            return id[p];
        }
    
        // 实现isConnected方法,查看元素p与元素q是否所属同一个集合
        @Override
        public boolean isConnected(int p, int q) {
            return id[p] == id[q];
        }
    
        // 实现unionElements方法,合并元素p和元素q所属集合
        @Override
        public void unionElements(int p, int q){
    
            int pID = find(p);
            int qID = find(q);
    
            if(pID == qID){
                return;
            }
            for(int i=0;i<id.length;i++){
                if(id[i]==pID){
                    id[i] = qID;
                }
            }
        }
    }
  • Quick FInd的时间复杂度分析
  • 技术分享图片

     

  • 第二版并查集Quick Union,业务逻辑如下:
  • public class UnionFind2 implements UF {
    
        private int[] parent;
    
        public UnionFind2(int size) {
    
            parent = new int[size];
            for (int i = 0; i < size; i++) {
                parent[i] = i;
            }
        }
    
        @Override
        public int getSize() {
            return parent.length;
        }
    
        private int find(int p) {
    
            if (p < 0 || p >= parent.length) {
                throw new IllegalArgumentException("p is out of bound.");
            }
            while (p != parent[p]) {
                p = parent[p];
            }
            return p;
        }
    
        // 实现isConnected方法,判断元素p与元素q是否同属一个集合
        @Override
        public boolean isConnected(int p, int q) {
            return parent[p] == parent[q];
        }
    
        // 实现unionElements方法,合并元素p和元素q所在集合
        @Override
        public void unionElements(int p, int q) {
    
            int pRoot = find(p);
            int qRoot = find(q);
    
            if (pRoot == qRoot) {
                return;
            }
            parent[pRoot] = qRoot;
        }
    }

     

  • Quick Union的时间复杂度分析
  • isConnected     O(h)   其中,h为树的高度
    unionElements   O(h)

     

  • 测试Quick Find和Quick Union的性能
  • 测试的业务逻辑如下:
  • import java.util.Random;
    
    public class Main {
    
        private static double testUF(UF uf, int m) {
    
            int size = uf.getSize();
            Random random = new Random();
            long startTime = System.nanoTime();
    
            for (int i = 0; i < m; i++) {
                int a = random.nextInt(size);
                int b = random.nextInt(size);
                uf.unionElements(a, b);
            }
    
            for (int i = 0; i < m; i++) {
                int a = random.nextInt(size);
                int b = random.nextInt(size);
                uf.isConnected(a, b);
            }
    
            long endTime = System.nanoTime();
            return (endTime - startTime) / 1000000000.0;
        }
    
        public static void main(String[] args) {
            int size = 100000;
            int m = 10000;
    
            UnionFind1 uf1 = new UnionFind1(size);
            double time1 = testUF(uf1, m);
            System.out.println("Quick Find, time: " + time1 + " s");
    
            UnionFind2 uf2 = new UnionFind2(size);
            double time2 = testUF(uf2, m);
            System.out.println("Quick Union, time: " + time2 + " s");
        }
    }

     

  • 输出结果:
  • Quick Find, time: 0.272248873 s
    Quick Union, time: 0.001273318 s

     

5.. Quick Union的优化
  • 对unionElements方法进行优化,使元素少的节点指向元素多的节点
  • 优化后的业务逻辑如下:
  • public class UnionFind3 implements UF {
    
        private int[] parent;
        private int[] sz;
    
        public UnionFind3(int size) {
    
            parent = new int[size];
            sz = new int[size];
    
            for (int i = 0; i < size; i++) {
                parent[i] = i;
                sz[i] = 1;
            }
        }
    
        @Override
        public int getSize() {
            return parent.length;
        }
    
        private int find(int p) {
    
            if (p < 0 || p >= parent.length) {
                throw new IllegalArgumentException("p is out of bound.");
            }
            while (p != parent[p]) {
                p = parent[p];
            }
            return p;
        }
    
        // 实现isConnected方法,判断元素p与元素q是否同属一个集合
        @Override
        public boolean isConnected(int p, int q) {
            return parent[p] == parent[q];
        }
    
        // 实现unionElements方法,合并元素p和元素q所在集合
        @Override
        public void unionElements(int p, int q) {
    
            int pRoot = find(p);
            int qRoot = find(q);
    
            if (pRoot == qRoot) {
                return;
            }
            if (sz[pRoot] < sz[qRoot]) {
                parent[pRoot] = qRoot;
                sz[qRoot] += sz[pRoot];
            }else{
                parent[qRoot] = pRoot;
                sz[pRoot]+=sz[qRoot];
            }
        }
    }

     

  • 对unionElements方法进行优化,使深度浅节点指向深度更深的节点
  • 优化后的业务逻辑如下:
  • public class UnionFind4 implements UF {
    
        private int[] parent;
        private int[] rank;
    
        public UnionFind4(int size) {
    
            parent = new int[size];
            rank = new int[size];
    
            for (int i = 0; i < size; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }
    
        @Override
        public int getSize() {
            return parent.length;
        }
    
        private int find(int p) {
    
            if (p < 0 || p >= parent.length) {
                throw new IllegalArgumentException("p is out of bound.");
            }
            while (p != parent[p]) {
                p = parent[p];
            }
            return p;
        }
    
        // 实现isConnected方法,判断元素p与元素q是否同属一个集合
        @Override
        public boolean isConnected(int p, int q) {
            return parent[p] == parent[q];
        }
    
        // 实现unionElements方法,合并元素p和元素q所在集合
        @Override
        public void unionElements(int p, int q) {
    
            int pRoot = find(p);
            int qRoot = find(q);
    
            if (pRoot == qRoot) {
                return;
            }
            if (rank[pRoot] < rank[qRoot]) {
                parent[pRoot] = qRoot;
            } else if (rank[qRoot] < rank[pRoot]) {
                parent[qRoot] = pRoot;
            } else {
                parent[pRoot] = qRoot;
                rank[qRoot] += 1;
            }
        }
    }

     

  • 对find方法进行优化,实现简单路径压缩(非递归实现)
  • 优化后业务逻辑如下
  • public class UnionFind5 implements UF {
    
        private int[] parent;
        private int[] rank;
    
        public UnionFind5(int size) {
    
            parent = new int[size];
            rank = new int[size];
    
            for (int i = 0; i < size; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }
    
        @Override
        public int getSize() {
            return parent.length;
        }
    
        private int find(int p) {
    
            if (p < 0 || p >= parent.length) {
                throw new IllegalArgumentException("p is out of bound.");
            }
            while (p != parent[p]) {
                parent[p] = parent[parent[p]];         //   优化了这里
                p = parent[p];
            }
            return p;
        }
    
        // 实现isConnected方法,判断元素p与元素q是否同属一个集合
        @Override
        public boolean isConnected(int p, int q) {
            return parent[p] == parent[q];
        }
    
        // 实现unionElements方法,合并元素p和元素q所在集合
        @Override
        public void unionElements(int p, int q) {
    
            int pRoot = find(p);
            int qRoot = find(q);
    
            if (pRoot == qRoot) {
                return;
            }
            if (rank[pRoot] < rank[qRoot]) {
                parent[pRoot] = qRoot;
            } else if (rank[qRoot] < rank[pRoot]) {
                parent[qRoot] = pRoot;
            } else {
                parent[pRoot] = qRoot;
                rank[qRoot] += 1;
            }
        }
    }

     

  • 再次优化find方法,实现终极路径压缩(递归实现)
  • public class UnionFind6 implements UF {
    
        private int[] parent;
        private int[] rank;
    
        public UnionFind6(int size) {
    
            parent = new int[size];
            rank = new int[size];
    
            for (int i = 0; i < size; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }
    
        @Override
        public int getSize() {
            return parent.length;
        }
    
        private int find(int p) {
    
            if (p < 0 || p >= parent.length) {
                throw new IllegalArgumentException("p is out of bound.");
            }
            if (p != parent[p]) {
                parent[p] = find(parent[p]);        //   优化了这里
            }
            return parent[p];
        }
    
        // 实现isConnected方法,判断元素p与元素q是否同属一个集合
        @Override
        public boolean isConnected(int p, int q) {
            return parent[p] == parent[q];
        }
    
        // 实现unionElements方法,合并元素p和元素q所在集合
        @Override
        public void unionElements(int p, int q) {
    
            int pRoot = find(p);
            int qRoot = find(q);
    
            if (pRoot == qRoot) {
                return;
            }
            if (rank[pRoot] < rank[qRoot]) {
                parent[pRoot] = qRoot;
            } else if (rank[qRoot] < rank[pRoot]) {
                parent[qRoot] = pRoot;
            } else {
                parent[pRoot] = qRoot;
                rank[qRoot] += 1;
            }
        }
    }

     

 


以上是关于第三十一篇 玩转数据结构——并查集(Union Find)的主要内容,如果未能解决你的问题,请参考以下文章

数据结构高阶第十一篇——并查集(原理+实现+应用)

我的第三十一篇博客---mongo

开始写游戏 --- 第三十一篇

第三十一篇iOS 9版本适配

并查集(union/find)

并查集