并查集的实现与应用(力扣)
- 并查集的实现
- 力扣323 130 990
学习地址
并查集的实现
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的根节点为Qif (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的根节点为Qparent[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题*/@Testpublic void test_01() {int n = 11;// un中的parent[] 为 0-10int[][] 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);}}// 设置方向数组dint[][] d = new int[][]{{0, 1},{1, 0},{0, -1},{-1, 0}};// 三层循环结束后 与dummy链接到同一个分量的O应该都不被转变为Xfor (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)// 向四个方向探索是否存在Oif (board[x][y] == 'O') {unionFind.union((x * n + y), i * n + j);// 四个方向任意一个有相邻的就合并分量}}}}}// 将非dummy集合的O都设置为Xfor (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();}}@Testpublic 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;}@Testpublic void test_03() {String[] equations = {"a==b", "b!=c", "a==c"};System.out.println(equationsPossible(equations));}
}