• 《算法系列》之并查集


    简介

      并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受。即使在空间上勉强通过,运行的时间复杂度也极高,只能用并查集来描述。故并查集是一种树型的数据结构,可用于处理一些不相交集合(disjoint sets)的合并及查询问题。

    理论基础

      并查集(Union-find Data Structure)是一种树型的数据结构。它的特点是由子结点找到父亲结点,用于处理一些不交集(Disjoint Sets)的合并及查询问题。并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的的树根节点,就能确定它在哪个集合里。 并查集跟树有些类似,只不过它和树是相反的。在树这个数据结构里面,每个节点会记录它的子节点。而在并查集里,每个节点只会记录它的父节点。

    • 并(Union):将两个子集合并成同一个集合。
    • 查(Find):确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
    • 集(Set):代表这是一个以字典为基础的数据结构,它的基本功能是合并集合中的元素,查找集合中的元素。

    并查集的代码实现:

    class UnionFind {
        private Map father;
        
        public UnionFind() {
            father = new HashMap();
        }
        
        public void add(int x) {
            if (!father.containsKey(x)) {
                father.put(x, null);
            }
        }
        
        public void merge(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            
            if (rootX != rootY){
                father.put(rootX,rootY);
            }
        }
        
        public int find(int x) {
            int root = x;
            
            while(father.get(root) != null){
                root = father.get(root);
            }
            
            while(x != root){
                int original_father = father.get(x);
                father.put(x,root);
                x = original_father;
            }
            
            return root;
        }
        
        public boolean isConnected(int x, int y) {
            return find(x) == find(y);
        }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    解题心得

    • 前300道题里没多少关于并查集的题,但我们仍需要了解该解题方法。
    • 很多题,并查集并不是最优解法,但了解并查集解法会给人眼前一亮的感觉。
    • 只要找到了某个元素的的树根节点,就能确定它在哪个集合里。
    • 并查集的典型应用是有关连通分量的问题。
    • 并查集解决单个问题(添加,合并,查找)的时间复杂度都是O(1)。

    算法题目

    128. 最长连续序列

    在这里插入图片描述
    题目解析:建立一个并查集UF对象,用map存储,并用head指向共同的头结点,只有当head为空的时候其中size才有意义。
    代码如下:

    /**
     * 并查集
     */
    class Solution {
        public int longestConsecutive(int[] nums) {
            if(nums.length==0) return 0;
            int max=1;
            Map map = new HashMap<>();
            for(int i : nums){
                if(map.containsKey(i)) continue;
                if(!map.containsKey(i)){
                    map.put(i,new UF(null,1));
                }
                if(map.containsKey(i+1)){
                    UF head = map.get(i+1);
                    int size = ++head.size;
                    max=Math.max(size,max);
                    UF item=new UF(null,size);
                    head.head=item;
                    map.put(i,item);
                }
                if(map.containsKey(i-1)){
                    UF head = map.get(i-1);
                    while(head.head!=null){
                        head=head.head;
                    }
                    head.size=map.get(i).size+head.size;
                    max=Math.max(head.size,max);
                    map.get(i).head=head;
                }
            }
            return max;
    
        }
    }
    
    class UF{
        public UF head;
        public int size;
        public UF (UF head , int size ){
            this.head=head;
            this.size=size;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    130. 被围绕的区域

    在这里插入图片描述
    题目解析:使用Union-Find法,先将四条边的’O’和dummy连接 --> 内部的’O’都与之四个邻居的’O’连接。最后不和dummy连接的’O’都是要修改为’X’的位置,修改即可。
    代码如下:

    /**
     * 并查集
     */
    class Solution {
        public void solve(char[][] board) {
            // 注意二维数组的坐标[i,j]换算成一个数:i*n+j
            int m = board.length;
            int n = board[0].length;
            UF uf = new UF(m*n+1);
            // 最后一个位置留给dummy用
            int dummy = m*n;
            for(int i=0;i
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    200. 岛屿数量

    在这里插入图片描述
    题目解析:我们可以用并查集代替深搜和广搜搜索,为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为1,则将其与相邻四个方向上的1在并查集中进行合并。最终岛屿的数量就是并查集中连通分量的数目。
    代码如下:

    /**
     * 并查集
     */
    class Solution {
        class UnionFind {
            int count;
            int[] parent;
            int[] rank;
    
            public UnionFind(char[][] grid) {
                count = 0;
                int m = grid.length;
                int n = grid[0].length;
                parent = new int[m * n];
                rank = new int[m * n];
                for (int i = 0; i < m; ++i) {
                    for (int j = 0; j < n; ++j) {
                        if (grid[i][j] == '1') {
                            parent[i * n + j] = i * n + j;
                            ++count;
                        }
                        rank[i * n + j] = 0;
                    }
                }
            }
    
            public int find(int i) {
                if (parent[i] != i) parent[i] = find(parent[i]);
                return parent[i];
            }
    
            public void union(int x, int y) {
                int rootx = find(x);
                int rooty = find(y);
                if (rootx != rooty) {
                    if (rank[rootx] > rank[rooty]) {
                        parent[rooty] = rootx;
                    } else if (rank[rootx] < rank[rooty]) {
                        parent[rootx] = rooty;
                    } else {
                        parent[rooty] = rootx;
                        rank[rootx] += 1;
                    }
                    --count;
                }
            }
    
            public int getCount() {
                return count;
            }
        }
    
        public int numIslands(char[][] grid) {
            if (grid == null || grid.length == 0) {
                return 0;
            }
    
            int nr = grid.length;
            int nc = grid[0].length;
            int num_islands = 0;
            UnionFind uf = new UnionFind(grid);
            for (int r = 0; r < nr; ++r) {
                for (int c = 0; c < nc; ++c) {
                    if (grid[r][c] == '1') {
                        grid[r][c] = '0';
                        if (r - 1 >= 0 && grid[r-1][c] == '1') {
                            uf.union(r * nc + c, (r-1) * nc + c);
                        }
                        if (r + 1 < nr && grid[r+1][c] == '1') {
                            uf.union(r * nc + c, (r+1) * nc + c);
                        }
                        if (c - 1 >= 0 && grid[r][c-1] == '1') {
                            uf.union(r * nc + c, r * nc + c - 1);
                        }
                        if (c + 1 < nc && grid[r][c+1] == '1') {
                            uf.union(r * nc + c, r * nc + c + 1);
                        }
                    }
                }
            }
    
            return uf.getCount();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    回到首页

    刷 leetcode 500+ 题的一些感受

    下一篇

    《算法系列》之模拟

  • 相关阅读:
    语义分割开源工具箱MMSegmentation安装及使用示例
    JVM-垃圾回收
    Java学习之包访问修饰符
    基于Springboot开发实现二手交易商城
    【MyBatis系列】Mybatis多表查询与动态SQL
    3D视觉——2.人体姿态估计(Pose Estimation)入门——OpenPose含安装、编译、使用(单帧、实时视频)
    索引——MySQL
    什么是关系选择器
    JSON
    AQS源码解析 8.共享模式_Semaphore信号量
  • 原文地址:https://blog.csdn.net/qq_22136439/article/details/126090290