一、理论
并查集的定义:
并查集是一种树型的数据结构,用于处理一些不交集的合并和查询问题。一般用数组实现。
Find:确定元素属于哪一个子集,它可以被用来确定两个元素是否属于同一个子集。
Union:将两个子集合并成同一个集合。
并查集的优化:
优化1: 降低rank,提高查询效率。合并时要考虑rank(即树结构的深度,在并查集里称为rank),优先让rank低的合并到rank高的。
优化2:路径压缩。不需要创建额外的内存,rank。 用的更多。
二、典型例题
☆☆①:二维网格中的小岛数量统计问题(LC200)
Note:【岛屿问题】是一个系列的网格问题,LC463->岛屿的周长、 LC695->岛屿的最大面积 、LC827->最大人工岛
此类问题参考题解:岛屿类问题的通用解法、DFS 遍历框架
方法1:DFS。一般DFS通常是在树或图结构上进行的,岛屿系列问题是网格DFS的典型代表。
方法2:BFS
方法3:并查集
代码1<DFS>
class Solution { public int numIslands(char[][] grid) { int count = 0; if (grid == null || grid.length == 0) return count; for (int i = 0; i < grid.length; i++) { for (int j = 0; j < grid[0].length; j++) { if (grid[i][j] == '1') { dfs(grid, i, j); count++; } } } return count; } public void dfs(char[][] grid, int r, int c) { // 超出网格范围。 "先污染后治理"->先往四个方向走一步再说,如果发现走出了网格范围再赶紧返回。 if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length) { return; } // 如果这个格子不是岛屿,返回。有两种情况:'0'海洋格子。'2'陆地格子(已遍历过) if (grid[r][c] != '1') { return; } grid[r][c] = '2'; // 避免重复遍历"兜圈子",标记已遍历过的格子 dfs(grid,r-1,c); dfs(grid,r+1,c); dfs(grid,r,c-1); dfs(grid,r,c+1); } }
②:计算矩阵中的朋友圈总数(LC547)
方法1:图的DFS。给定的矩阵看成图的邻接矩阵,问题可以变成求无向图连通块的个数。
方法2:图的BFS
方法3:并查集
代码1<DFS>
class Solution { public int findCircleNum(int[][] M) { /** * 方法1:图的DFS * 首先选择一个节点,访问任一相邻的节点。然后再访问这一节点的任一相邻节点。 * 这样不断遍历到没有未访问的相邻节点时,回溯到之前的节点进行访问。 */ int count = 0; if (M == null || M.length == 0) return count; boolean[] visited = new boolean[M.length]; for (int i = 0; i < M.length; i++) { if (!visited[i]) { dfs(M, visited, i); count++; } } return count; } public void dfs(int[][] M, boolean[] visited, int i) { visited[i] = true; for (int j = 0; j < M.length; j++) { if (!visited[j] && M[i][j] == 1) { dfs(M,visited,j); } } } }
代码3<并查集>
class Solution { public int findCircleNum(int[][] isConnected) { // 方法2:并查集, 底层基于数组实现 int n = isConnected.length; UnionFind uf = new UnionFind(n); for (int i = 0; i < n; i++) { // 将当前节点与其邻接点进行合并 for (int j = i + 1; j < n; j++) { if (isConnected[i][j] == 1) { uf.union(i, j); } } } return uf.size; } } class UnionFind{ int[] roots; int size; public UnionFind(int n) { roots = new int[n]; for (int i = 0; i < n; i++) { // 初始化时指向自己 roots[i] = i; } size = n; } public int find(int i) { if (i == roots[i]) { return i; } return roots[i] = find(roots[i]); } public void union(int p, int q) { int pRoot = find(p); int qRoot = find(q); if (pRoot != qRoot) { roots[pRoot] = qRoot; size --; } } }