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

时间:2021-03-15 20:39:05
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的时间复杂度分析
  • 第三十一篇 玩转数据结构——并查集(Union 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;
    }
    }
    }