并查集的实现与应用(力扣)

并查集的实现与应用(力扣)


学习地址

并查集的实现

package com.caoii;/*
 *@program:labu-pratice-study
 *@package:com.caoii
 *@author: Alan
 *@Time: 2024/4/12  21:53
 *@description: 并查集的实现
 */

public class UnionFind {
    // 记录连通分量
    private int count;

    // 节点X的父节点 是 parent[x]
    private int[] parent;

    //使用一个size数组  记录每棵树包含的节点数目
    //来让两个树合并的时候尽量让小的树接到大的树的下面
    //这样每次使用find向上找根节点的复杂度能相对减少
    //private int[] size;
    // 通过改造find函数 可将每个树都变成 真正高度为2
    // 即 每个子节点都直接与最高根节点相连的样式
    // 故size就不必再使用了

    // 构造函数 n 为 图的节点数目
    public UnionFind(int n) {
        this.count = n;
        // 一开始互不连通 则 连通分量的数目等于节点数目
        parent = new int[n];
        // 父节点指针初始时指向自己
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            //size[i] = 1;
        }
        // 若两个节点被连通 则其中任意一个节点的根节点指针指向另一个节点
    }

    /*将p和q 所在的连通分量进行 链接*/
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        // find方法获取两个树的最高根节点
        if (rootP == rootQ) {
            // 两棵树已经连通则最高根节点一定相同
            // 不需要 再次链接 直接退出方法
            return;
        }
        /*
        改进find之后不用size了
        //两棵树最高根节点不同的时候:
        //两棵树合并为一棵树  设置P的根节点为Q
        if (size[rootP] > size[rootQ]) {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
            // P树更高 则 把 Q树接在P树下面,让Q的父节点指针指向P
            // P的高度增加
            // 实际上此时所说的高度不是真的高度而是该树的全部节点个数
        } else {
            parent[rootP] = rootQ;
            size[rootQ] += size[rootP];
        }*/
        //两棵树最高根节点不同的时候:
        //两棵树合并为一棵树  设置P的根节点为Q
        parent[rootP] = rootQ;
        count--;
        // 两个分量合二为一   分量数目减一
    }

    /*返回某个节点X的最高根节点*/
    public int find(int x) {
        /*传统方法  逐次向上寻找最高根节点
        while (parent[x] != x) {
            x = parent[x];
        }
        return x;
        // 若根节点指针指向自己,则返回自己
        // 若根节点指针没有指向自己,则把根节点指针指向的元素赋值给X 并循环判断
        // 若 3-->5-->6  则 X=3时执行:x=5 ——> 5!=parent[5]=6 ——> x=6 ——> 6=parent[6]=6 ——> return 6*/
        // 改进方法 在寻找最高根节点的过程中
        // 将树构造为 真实高度为2 所有子节点都与根节点直接相连的形式:
        if (parent[x] != x) {
            // x的根节点 不是 x 自己
            // 则x 存在根节点
            parent[x] = find(parent[x]);
            // 递归运算
            // 最后:
        }
        return parent[x];
        // 递归出口: 递归到最高层根节点 此时 x==parent[x] 所以返回x
        // 则 次高层处节点为y,  parent[y] = find(parent[y]) = x
        // 即次高层处节点的父指针指向最高节点
        // 同理 次次高层处节点为z,  parent[z] = find(parent[z]) = find(y) = parent[y] = x
        // 即次次高层处节点的父指针指向最高节点x
        // 以此类推
        // 最后结果就是所有子节点都直接与根节点直接相连  树的真是高度为2
    }

    /*判断 p 和 q 是否连通*/
    public boolean connected(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        return rootP == rootQ;
        // 若两个树的最高节点相同则p与q连通
    }

    /*返回图中有多少个连通分量*/
    public int count() {
        return count;
    }
}

力扣323 130 990

package com.caoii;/*
 *@program:labu-pratice-study
 *@package:com.caoii
 *@author: Alan
 *@Time: 2024/4/12  23:25
 *@description: 并查集相关题目测试
 */

import org.junit.jupiter.api.Test;

public class UFTest {
    /*
     * 力扣323题
     * 给你输入一个包含 n 个节点的图,用一个整数 n 和一个数组 edges 表示,
     * 其中 edges[i][j] = [ai, bi] 表示图中节点 ai 和 bi 之间有一条边。
     * 请你计算这幅图的连通分量个数。*/
    public int countComponents(int n, int[][] enges) {
        int count = n;
        // 初始时 连通分量个数等于节点个数
        UnionFind unionFind = new UnionFind(n);
        for (int[] e : enges) {
            unionFind.union(e[0], e[1]);
            // 链接 ai 与 bi
        }
        count = unionFind.count();
        // 返回 完成全部链接后 最少的连通分量个数
        return count;
    }

    /*测试323题*/
    @Test
    public void test_01() {
        int n = 11;
        // un中的parent[] 为 0-10
        int[][] enges = {
                {0, 6}, {6, 0},
                {1, 2}, {1, 3}, {2, 1}, {2, 3}, {2, 4},
                {3, 1}, {3, 2}, {3, 5}, {4, 2},
                {6, 7}, {7, 6},
                {8, 9}, {9, 8}, {9, 10}, {10, 9}
        };
        System.out.print("该无向图的连通分量个数为: " + countComponents(n, enges));
    }


    /*力扣130
    给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' ,
    找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充
    被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。
    任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。
    如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。*/
    public void solve(char[][] board) {
        // 用并查集解决
        // 将二维数组映射为一维数组
        if (board.length == 0) {
            return;
        }
        int m = board.length;
        //行数
        int n = board[0].length;
        //列数
        // board[a][b] ==> temp[x*n+y]  x从0-(board.length-1)
        // temp的index从0-(lengthAll-1)
        UnionFind unionFind = new UnionFind(m * n + 1);
        // 用一个一维数组设置并查集对象
        // 多设置一个空位存储一个虚构的根节点
        int dummy = m * n; // 此根节点的索引值为m*n
        // 将首列与末列的O与dummy相连
        for (int i = 0; i < m; i++) {
            if (board[i][0] == 'O') {
                unionFind.union((i * n + 0), dummy);
            }
            if (board[i][n - 1] == 'O') {
                unionFind.union((i * n + n - 1), dummy);
            }
        }
        // 将首行与末行的O与dummy 相连
        for (int j = 0; j < n; j++) {
            if (board[0][j] == 'O') {
                unionFind.union((0 * n + j), dummy);
            }
            if (board[m - 1][j] == 'O') {
                unionFind.union((m - 1) * n + j, dummy);
            }
        }

        // 设置方向数组d
        int[][] d = new int[][]{
                {0, 1},
                {1, 0},
                {0, -1},
                {-1, 0}
        };

        // 三层循环结束后 与dummy链接到同一个分量的O应该都不被转变为X
        for (int i = 1; i < m - 1; i++) {
            for (int j = 1; j < n - 1; j++) {
                if (board[i][j] == 'O') {
                    for (int k = 0; k < 4; k++) {
                        int x = i + d[k][0];
                        int y = j + d[k][1];
                        // i 与 j 分别 加(0,1)(1,0)(0,-1)(-1,0)
                        // 向四个方向探索是否存在O
                        if (board[x][y] == 'O') {
                            unionFind.union((x * n + y), i * n + j);
                            // 四个方向任意一个有相邻的就合并分量
                        }
                    }
                }
            }
        }
        // 将非dummy集合的O都设置为X
        for (int i = 1; i < m - 1; i++) {
            for (int j = 1; j < n - 1; j++) {
                if (board[i][j] == 'O') {
                    if (!unionFind.connected(dummy, i * n + j)) {
                        board[i][j] = 'X';
                    }
                }
            }
        }

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                System.out.print(board[i][j] + " ");
            }
            System.out.println();
        }
    }

    @Test
    public void test_02() {
        char[][] board = new char[][]{
                {'X', 'X', 'X', 'X'},
                {'X', 'O', 'O', 'X'},
                {'X', 'X', 'O', 'X'},
                {'X', 'O', 'X', 'X'}
        };
        solve(board);
    }

    /*
    力扣 990 题
    给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,
    并采用两种不同的形式之一:"a==b" 或 "a!=b"。
    在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。
    只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false

    示例 1:
    输入:["a==b","b!=a"]
    输出:false
    解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。
    没有办法分配变量同时满足这两个方程。
    示例 2:
    输入:["b==a","a==b"]
    输出:true
    解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。
    示例 3:
    输入:["a==b","b==c","a==c"]
    输出:true
    示例 4:
    输入:["a==b","b!=c","c==a"]
    输出:false
    示例 5:
    输入:["c==c","b==d","x!=z"]
    输出:true
    */
    public boolean equationsPossible(String[] equations) {
        UnionFind unionFind = new UnionFind(26);
        for (int i = 0; i < equations.length; i++) {
            if (equations[i].charAt(1) == equations[i].charAt(2)) {
                // == 情况
                unionFind.union((int) (equations[i].charAt(0) - 'a'), (int) (equations[i].charAt(3) - 'a'));
            }
        }
        for (int i = 0; i < equations.length; i++) {
            if (equations[i].charAt(1) != equations[i].charAt(2)) {
                // != 情况
                if (unionFind.connected((int) (equations[i].charAt(0) - 'a'), (int) (equations[i].charAt(3) - 'a'))) {
                    // 如果 在不等于的条件下 发现 他俩已经放入同一个集合了
                    return false;
                }
            }
        }
        return true;
    }

    @Test
    public void test_03() {
        String[] equations = {"a==b", "b!=c", "a==c"};
        System.out.println(equationsPossible(equations));
    }
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值