美文网首页
算法学习:并查集

算法学习:并查集

作者: alex很累 | 来源:发表于2023-03-05 00:09 被阅读0次

一、背景

当我们遇到朋友圈问题时:
a与b是朋友,b和c是朋友,那么我们可以将a、b、c看作一个朋友圈...此外,还有d、f、e等人,他们之间也存在着朋友关系或者没有朋友关系。
问:有多少个朋友圈?d、e是否有朋友关系?等等问题,我们都可以用并查集解决。

二、概念及其实现原理

并查集是一种用于解决组团、配对问题的数据结构,它维护的是一堆集合而不是一个集合

它需要实现的功能有:

  • makeSet(s):建立一个新的并查集,其中包含 s 个单元素集合。


    初始化
  • unionSet(x, y):把元素 x 和元素 y 所在的集合合并,要求 x 和 y 所在的集合不相交,如果相交则不合并。


    合并

    但是,这样的结构是不方便使用的,我们可以压缩路径,将子节点直接指向带头节点。(可以在find的过程中处理)


    压缩路径
  • find(x):找到元素 x 所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。

三、Java实现

class UnionFind {
    // 分组个数
    int count;
    // 数组的第i位表示i的领头结点
    int[] parent;

    // 初始化
    public UnionFind(int n) {
        count = n;
        parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    // 查找x的领头结点
    public int find(int x) {
        List<Integer> list = new ArrayList<>();
        while (x != parent[x]) {
            list.add(x);
            parent[x] = parent[parent[x]];
            x = parent[x];
        }
        // 优化路径
        for (int item : list) {
            parent[item] = x;
        }
        return x;
    }

    // 合并分组
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return;
        }
        parent[rootY] = rootX;
        count--;
    }
}

五、相关算法题

547. 省份数量

问题描述

n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。

示例

解题思路

实质是朋友圈问题,直接套用并查集即可。

代码示例(JAVA)

class Solution {
    public int findCircleNum(int[][] isConnected) {
        int people = isConnected.length;
        UnionFind unionFind = new UnionFind(people);
        for (int i = 0; i < people; i++) {
            for (int j = 0; j < people; j++) {
                if (isConnected[i][j] == 1) {
                    unionFind.union(i, j);
                }
            }
        }
        return unionFind.count;
    }

    class UnionFind {
        // 分组个数
        int count;
        // 数组的第i位表示i的领头结点
        int[] parent;

        // 初始化
        public UnionFind(int n) {
            count = n;
            parent = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
            }
        }

        // 查找x的领头结点
        public int find(int x) {
            List<Integer> list = new ArrayList<>();
            while (x != parent[x]) {
                list.add(x);
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            // 优化路径
            for (int item : list) {
                parent[item] = x;
            }
            return x;
        }

        // 合并分组
        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX == rootY) {
                return;
            }
            parent[rootY] = rootX;
            count--;
        }
    }
}

200. 岛屿数量

问题描述

给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

示例

解题思路

这道题同样可以看作朋友圈问题,假设二维网格的长宽为mn,那么共有m * n个人;那么朋友关系如何建立?可以通过节点四周的连接情况进行判断。
另外要注意的是,并查集中的count要记录的是陆地的分组数量。

代码示例(JAVA)

class Solution {

    static int[][] xys = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

    public int numIslands(char[][] grid) {
        int m = grid.length;
        int n = grid[0].length;

        // 建并查集
        UnionFind unionFind = new UnionFind(grid);
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == '1') {
                    for (int[] xy : xys) {
                        // 遍历周围的陆地/水情况,合并分组
                        if (i + xy[0] >= 0 && i + xy[0] < m && j + xy[1] >= 0 && j + xy[1] < n
                                && grid[i + xy[0]][j + xy[1]] == '1') {
                            unionFind.union(i * n + j, (i + xy[0]) * n + (j + xy[1]));
                        }
                    }
                }
            }
        }

        return unionFind.count;
    }

    class UnionFind {
        // 分组个数(陆地)
        int count;
        // 数组的第i位表示i的领头结点
        int[] parent;

        // 初始化
        public UnionFind(char[][] grid) {
            count = 0;
            int m = grid.length;
            int n = grid[0].length;
            parent = new int[m * n];
            for (int i = 0; i < m * n; i++) {
                parent[i] = i;
                if (grid[i / n][i % n] == '1') {
                    count++;
                }
            }
        }

        // 查找x的领头结点
        public int find(int x) {
            List<Integer> list = new ArrayList<>();
            while (x != parent[x]) {
                list.add(x);
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            // 优化路径
            for (int item : list) {
                parent[item] = x;
            }
            return x;
        }

        // 合并分组
        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX == rootY) {
                return;
            }
            parent[rootY] = rootX;
            count--;
        }
    }
}

130. 被围绕的区域

问题描述

给你一个 m x n 的矩阵 board ,由若干字符 XO ,找到所有被 X 围绕的区域,并将这些区域里所有的 OX 填充。

示例

解题思路

找到问题的核心:保留边界的O及与其有连通性的O,其他均为X
我们依旧可以使用并查集,我们可以建一个虚拟节点,将边界的O及与其有连通性的O与其建立分组关系;最后,遍历矩阵,将和这个虚拟节点没有分组关系的节点,设为X

代码示例(JAVA)

class Solution {
    static int[][] xys = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

    public void solve(char[][] board) {
        int m = board.length;
        int n = board[0].length;

        // 建立并查集,多出的节点为虚拟节点
        UnionFind unionFind = new UnionFind(m * n + 1);
        int virtualNode = m * n;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (board[i][j] == 'O') {
                    // 周围的'O'直接和虚拟节点合并
                    if (i == 0 || i == m - 1 || j == 0 || j == n - 1) {
                        unionFind.union(i * n + j, virtualNode);
                    } else {
                        // 中间的'O'和周围的'O'合并
                        for (int[] xy : xys) {
                            if (i + xy[0] >= 0 && i + xy[0] < m
                                    && j + xy[1] >= 0 && j + xy[1] < n
                                    && board[i + xy[0]][j + xy[1]] == 'O') {
                                unionFind.union(i * n + j, (i + xy[0]) * n + j + xy[1]);
                            }
                        }
                    }
                }
            }
        }
        // 和虚拟节点没有连通性的'O'改为'X'
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (unionFind.find(i * n + j) != unionFind.find(virtualNode)) {
                    board[i][j] = 'X';
                }
            }
        }
    }

    class UnionFind {
        // 分组个数
        int count;
        // 数组的第i位表示i的领头结点
        int[] parent;

        // 初始化
        public UnionFind(int n) {
            count = n;
            parent = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
            }
        }

        // 查找x的领头结点
        public int find(int x) {
            List<Integer> list = new ArrayList<>();
            while (x != parent[x]) {
                list.add(x);
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            // 优化路径
            for (int item : list) {
                parent[item] = x;
            }
            return x;
        }

        // 合并分组
        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX == rootY) {
                return;
            }
            parent[rootY] = rootX;
            count--;
        }
    }
}

相关文章

  • 从并查集Union Find算法谈算法解决实际问题的过程

    从并查集Union Find算法谈算法解决实际问题的过程算法并查集算法寻找路径 从并查集Union Find算法谈...

  • 并查集练习

    并查集 参考《算法笔记》 三要素:并,查,集。 并差集与一个算法有关,kruskal算法,就可通过并查集实现。 1...

  • 并查集 的总结

    一、并查集的理解 算法学习笔记(1) : 并查集[https://zhuanlan.zhihu.com/p/936...

  • 深入理解并查集

    并查集是一种树形结构,它是由并查集算法进行维护的。而并查集算法(Union-find-algorithm),顾名思...

  • 并查集入门使用

    本文参考自《算法笔记》并查集篇 并查集的定义 什么是并查集?并查集可以理解为是一种维护数据集合的结构。名字中并查集...

  • 模板

    并查集 拓扑排序 Floyd算法 Dijkstra算法

  • 最小生成树

    Kruskal算法 伪代码: 并查集:

  • [算法] 并查集

    并查集维护元素的分组情况,可以处理不相交集合的合并及查询问题,用于求解图的连通等问题。 并查集使用树型结构实现(但...

  • 并查集算法

    看到个有意思的题 岛问题一个矩阵中只有0和1两种值,每个位置都可以和自己的上下左右四个位置相连,如果有一片1连在一...

  • 算法:并查集

    LeetCode经典题目 130. 被围绕的区域[https://leetcode-cn.com/problems...

网友评论

      本文标题:算法学习:并查集

      本文链接:https://www.haomeiwen.com/subject/snzyldtx.html