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)的更多相关文章

  1. 第三十三篇 玩转数据结构——红黑树(Read Black Tree)

    1.. 图解2-3树维持绝对平衡的原理: 2.. 红黑树与2-3树是等价的 3.. 红黑树的特点 简要概括如下: 所有节点非黑即红:根节点为黑:NULL节点为黑:红节点孩子为黑:黑平衡 4.. 实现红 ...

  2. 第三十篇 玩转数据结构——字典树(Trie)

          1.. Trie通常被称为"字典树"或"前缀树" Trie的形象化描述如下图: Trie的优势和适用场景 2.. 实现Trie 实现Trie的业务无 ...

  3. 算法手记 之 数据结构(并查集详解)(POJ1703)

    <ACM/ICPC算法训练教程>读书笔记-这一次补上并查集的部分.将对并查集的思想进行详细阐述,并附上本人AC掉POJ1703的Code. 在一些有N个元素的集合应用问题中,通常会将每个元 ...

  4. ACM数据结构-并查集

    ACM数据结构-并查集   并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合 ...

  5. 【题解】P2024 [NOI2001]食物链 - 数据结构 - 并查集

    P2024 [NOI2001]食物链 声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 题目描述 动物王国中有三类动物 \(A,B ...

  6. 图论&数据结构——并查集

    Wikioi 4246 NOIP模拟赛Day2T1 奶牛的身高  题目描述 Description 奶牛们在FJ的养育下茁壮成长.这天,FJ给了奶牛Bessie一个任务,去看看每个奶牛场中若干只奶牛的 ...

  7. 《挑战程序设计竞赛》2.4 数据结构-并查集 POJ1182 2236 1703 AOJ2170

    POJ1182 http://poj.org/problem?id=1182 题目 难得的中文题... 食物链 Time Limit: 1000MS Memory Limit: 10000K Tota ...

  8. 第三十四篇 玩转数据结构——哈希表(HashTable)

    1.. 整型哈希函数的设计 小范围正整数直接使用 小范围负整数整体进行偏移 大整数,通常做法是"模一个素数"   2.. 浮点型哈希函数的设计 转成整型进行处理   3.. 字符串 ...

  9. 第三十二篇 玩转数据结构——AVL树(AVL Tree)

          1.. 平衡二叉树 平衡二叉树要求,对于任意一个节点,左子树和右子树的高度差不能超过1. 平衡二叉树的高度和节点数量之间的关系也是O(logn) 为二叉树标注节点高度并计算平衡因子 AVL ...

随机推荐

  1. MyBatis mapper文件中使用常量

    MyBatis mapper文件中使用常量 Java 开发中会经常写一些静态常量和静态方法,但是我们在写sql语句的时候会经常用到判断是否等于 //静态类 public class CommonCod ...

  2. Java-天平称重

    题目: 用天平称重时,我们希望用尽可能少的砝码组合称出尽可能多的重量.如果只有5个砝码,重量分别是1,3,9,27,81 则它们可以组合称出1到121之间任意整数重量(砝码允许放在左右两个盘中).本题 ...

  3. Arduino上搭建ESP8266环境

    我尝试了各种方法都无法在Arduino上安装ESP8266的环境,最后发现离线安装最稳妥. 1. 下载安装包,提取码:pktw 2. 将安装包内所有文件拷贝到C:\Users\Administrato ...

  4. P3206 [HNOI2010]城市建设 [线段树分治+LCT维护动态MST]

    Problem 这题呢 就边权会在某一时刻变掉-众所周知LCT不支持删边的qwq- 所以考虑线段树分治- 直接码一发 如果 R+1 这个时间修改 那就当做 [L,R] 插入了一条边- 然后删的边和加的 ...

  5. how to use bookdown

    模板下载 {#moban} 谢益辉 通用模板:https://github.com/rstudio/bookdown-demo 李东风中文模板: http://www.math.pku.edu.cn/ ...

  6. LOJ#508. 「LibreOJ NOI Round #1」失控的未来交通工具

    题意 一个带边权无向图,有两种操作:加边以及询问在\(x,x+b,...,x+(c-1)b\)这些数中,有多少个数存在至少一条与之模\(m\)同余的从\(u\)到\(v\)的路径(可以不是简单路径). ...

  7. 咸鱼的ACM之路:DFS水题集

    DFS的核心就是从一种状态出发,转向任意的一个可行状态,直到达到结束条件为止.(个人理解) 下面全是洛谷题,毕竟能找到测试点数据的OJ我就找到这一个....在其他OJ上直接各种玄学问题... P159 ...

  8. 500kuai

    https://www.bilibili.com/bangumi/media/md11653495/?spm_id_from=666.10.b_62616e67756d695f64657461696c ...

  9. 使用nohup不产生log文件方法

    思想 无法阻止nohup产生日志可以将其定向到空文件实现 实现 $ nohup xxx >/dev/null 2>&1 &

  10. c++ STL vector初步学习

    /*vector(向量):是一种顺序容器,,动态数组,事实上和数组差不多,但它比数组更优越.一般来说数组不能动态拓展,因此在程序运行的时候不是浪费内存,就是造成越界.而vector正好弥补了这个缺陷, ...