• 第三十一篇 玩转数据结构——并查集(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;
              }
          }
      }
  • 相关阅读:
    Servlet中request乱码问题解决
    java对cookie的操作
    EL表达式
    MYSQL安装以及卸载 -- WINDOWS
    java接口
    HttpClient配置及运用(二)
    JVM的内存空间
    孤寂的欢愉
    论神性与人性
    使用NPOI读取Excel出错
  • 原文地址:https://www.cnblogs.com/xuezou/p/9305792.html
Copyright © 2020-2023  润新知