• 并查集的优化及应用


    2018-05-01 15:13:08

    并查集是一个时空复杂度非常优越的数据结构,并且通过优化后其复杂度为<O(1),O(n)>。

    并查集的优化主要有两个方面:

    • 路径压缩
    • 按rank来合并

    路径压缩:

    按rank合并:

    public class UnionFindSet {
        private int[] parent;
        private int[] rank;
    
        public UnionFindSet(int n) {
            parent = new int[n + 1];
            rank = new int[n + 1];
            for (int i = 0; i < n + 1; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }
    
        public int find(int i) {
            if (parent[i] != i)
                parent[i] = find(parent[i]);
            return parent[i];
        }
    
        public boolean union(int i, int j) {
            int pi = find(i);
            int pj = find(j);
    
            if (pi == pj) return false;
    
            if (rank[pi] > rank[pj])
                parent[pj] = pi;
            else if (rank[pi] < rank[pj])
                parent[pi] = pj;
            else {
                parent[pj] = pi;
                rank[pi]++;
            }
            return true;
        }
    }
    
    • 684. Redundant Connection

    问题描述:

    问题求解:

    树形下的无向图判断环路问题,图的描述方式是采用边集。

    并查集本身就是树形结构,而树是一个无向图,具体来说,树是一个无环的连通图,所以本题可以直接使用并查集来进行求解。

        public int[] findRedundantConnection(int[][] edges) {
            UnionFindSet ufs = new UnionFindSet(edges.length + 1);
            int[] res = new int[2];
            for (int[] pair : edges) {
                if (!ufs.union(pair[0], pair[1])) {
                    res[0] = Math.min(pair[0], pair[1]);
                    res[1] = Math.max(pair[0], pair[1]);
                    break;
                }
            }
            return res;
        }
    

    2019.04.21

        public int[] findRedundantConnection(int[][] edges) {
            int n = edges.length;
            int[] parent = new int[n + 1];
            for (int i = 1; i <= n; i++) parent[i] = i;
            for (int[] edge : edges) {
                if (!union(parent, edge[0], edge[1])) {
                    Arrays.sort(edge);
                    return edge;
                }
            }
            return null;
        }
        
        private int find(int[] parent, int i) {
            if (parent[i] != i) {
                parent[i] = find(parent, parent[i]);
            }
            return parent[i];
        }
        
        private boolean union(int[] parent, int i, int j) {
            int pi = find(parent, i);
            int pj = find(parent, j);
            if (pi == pj) return false;
            parent[pj] = pi;
            return true;
        }
    

      

    • 547. Friend Circles

    问题描述:

    问题求解:

    class Solution {
        public int findCircleNum(int[][] M) {
            UnionFindSet ufs = new UnionFindSet(M.length);
            int res = 0;
            for (int i = 0; i < M.length; i++) {
                for (int j = 0; j < i; j++) {
                    if (M[i][j] == 1)
                        ufs.union(i, j);
                }
            }
            for (int i = 0; i < ufs.parent.length; i++) 
                if (ufs.parent[i] == i) res++;
            return res;
        }
    }
    
    class UnionFindSet {
        public int[] parent;
        private int[] rank;
    
        public UnionFindSet(int n) {
            parent = new int[n];
            rank = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }
    
        public int find(int i) {
            if (parent[i] != i)
                parent[i] = find(parent[i]);
            return parent[i];
        }
    
        public boolean union(int i, int j) {
            int pi = find(i);
            int pj = find(j);
    
            if (pi == pj) return false;
    
            if (rank[pi] > rank[pj])
                parent[pj] = pi;
            else if (rank[pi] < rank[pj])
                parent[pi] = pj;
            else {
                parent[pj] = pi;
                rank[pi]++;
            }
            return true;
        }
    }
    
    • 765. Couples Holding Hands

    问题描述:

    问题求解:

    这里有一个O(n)的做法, 一次考虑两个凳子,假设他们不为夫妇,为了让这两个位置坐的恰好是一对夫妇,那么我们就需要调整其中一个人的位置,如此调整直到所有的夫妇相邻,交换的次数就是答案。下面给出证明。

    将给定的row抽象成一个n个顶点的无向图(可能包含重边),例如:

    (_ _) (_ _) ... (_ _)
    (v1 ) (v2 ) ... (vn )

    vi和vj之间存在边当且仅当vi和vj中存在一对夫妇,例如vi = (0,2),vj = (1, 4)存在一对夫妇(0, 1),而vi = (0, 2),vj = (1, 3)之间则存在两对夫妇(0, 1)(2, 3),此vi和vj存在重边。

    考虑row数组形成的无向图,可以肯定要么是孤立的单个节点,要么是多个孤立的圈,例如row = [0, 1]是一个孤立的点、row = [0, 2, 1, 3]则包含一个圈v1, v2、row = [0, 3, 4, 1, 2, 5, 6, 8, 7, 9],包含两个孤立的圈v1, v2, v3和v4, v5。

    对于一个圈来说,假设他有n个节点,那么至少需要n-1次交换即可让每个夫妇相邻。有了这个结论,假定row数组有n个点,m个孤立的圈,那么至少需要n-m次交换即可。

        public int minSwapsCouples(int[] row) {
            int n = row.length / 2;
            int[] parent = new int[n];
            int cnt = n;
            for (int i = 0; i < n; i++) parent[i] = i;
            for (int i = 0; i < n * 2; i += 2) {
                if (union(parent, row[i] / 2, row[i + 1] / 2)) cnt--;
            }
            return n - cnt;
        }
        
        private int find(int[] parent, int i) {
            if (parent[i] != i) {
                parent[i] = find(parent, parent[i]);
            }
            return parent[i];
        }
        
        private boolean union(int[] parent, int i, int j) {
            int pi = find(parent, i);
            int pj = find(parent, j);
            if (pi == pj) return false;
            parent[pj] = pi;
            return true;
        }
    • 947. Most Stones Removed with Same Row or Column

    问题描述:

    问题求解:

    这个问题可以转化为求图中连通数的问题,也就是经典的陆地数量问题。对于x | y相等的两个石头我们需要建立他们之间的联系。

    经典的连通子树问题可以使用dfs进行求解,从dfs的算法过程我们可以看到其实是一棵以起始点为root的树,因此,在这次dfs中我们总可以从叶子节点开始选取,知道最后只剩下root节点。

    最终的答案就是所有的stones的数目 - 连通块的数目。

    这里并不打算使用dfs来进行求解,将使用ufs来进行求解。

    使用并查集并不需要那么形式化的专门使用一个类来进行表征,这就是一个简单的数据结构,只需要在使用前进行定义就好了,另外,由于parent的数目范围不确定,所以在很多时候使用数组并不是一个合适的选择,使用hash表更能方便我们解决问题,本题就是使用hash表来进行的并查集的实现。

        public int removeStones(int[][] stones) {
            Map<Integer, Integer> parent = new HashMap<>();
            int n = stones.length;
            for (int[] stone : stones) {
                union(parent, stone[0], stone[1] + 10000);
            }
            int cnt = 0;
            for (int key : parent.keySet()) {
                if (parent.get(key) == key) cnt++;
            }
            return n - cnt;
        }
        
        private int find(Map<Integer, Integer> parent, int i) {
            if (!parent.containsKey(i)) parent.put(i, i);
            if (parent.get(i) != i) {
                parent.put(i, find(parent, parent.get(i)));
            }
            return parent.get(i);
        }
        
        private boolean union(Map<Integer, Integer> parent, int i, int j) {
            int pi = find(parent, i);
            int pj = find(parent, j);
            if (pi == pj) return false;
            parent.put(pj, pi);
            return true;
        }
    • 959. Regions Cut By Slashes

    问题描述:

    问题求解:

    主要问题就是怎么将字符串的输入进行转化,这里采用的转化方式是将每个cell看成4个区域,0-3。根据不同的情况可以将各个区域进行合并,这样本题就又变成了并查集问题。

        int n;
        
        public int regionsBySlashes(String[] grid) {
            n = grid.length;
            int[] parent = new int[n * n * 4];
            for (int i = 0; i < parent.length; i++) parent[i] = i;
            int size = n * n * 4;
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (i > 0 && union(parent, getIdx(i - 1, j, 2), getIdx(i, j, 0))) size--;
                    if (j > 0 && union(parent, getIdx(i, j - 1, 1), getIdx(i, j, 3))) size--;
                    if (grid[i].charAt(j) != '/') {
                        if (union(parent, getIdx(i, j, 0), getIdx(i, j, 1))) size--;
                        if (union(parent, getIdx(i, j, 2), getIdx(i, j, 3))) size--;
                    }
                    if (grid[i].charAt(j) != '\') {
                        if (union(parent, getIdx(i, j, 0), getIdx(i, j, 3))) size--;
                        if (union(parent, getIdx(i, j, 1), getIdx(i, j, 2))) size--;
                    }
                }
            }
            return size;
        }
        
        private int find(int[] parent, int i) {
            if (parent[i] != i) {
                parent[i] = find(parent, parent[i]);
            }
            return parent[i];
        }
        
        private boolean union(int[] parent, int i, int j) {
            int pi = find(parent, i);
            int pj = find(parent, j);
            if (pi == pj) return false;
            parent[pj] = pi;
            return true;
        }
        
        private int getIdx(int i, int j, int num) {
            return i * n * 4 + j * 4 + num;
        }
    

      

      

  • 相关阅读:
    第五章 Mybatis注解
    第四章 Mbatis高级查询
    第三章 Mybatis动态Sql
    第二章 Mybatis映射文件
    第一章 初识Mybatis
    Mybatis大纲设计
    项目总结
    第二周项目功能实现
    第一周项目功能实现
    客车网上售票系统需求分析
  • 原文地址:https://www.cnblogs.com/hyserendipity/p/8976359.html
Copyright © 2020-2023  润新知