赞
踩
并查集是一种树型的数据结构,用于处理一些不交集的合并及查询问题
并查集主要包含以下几种基本操作:
init(s)
:建立一个新的并查集,其中包含s个单元素集合union(x, y)
:把元素x和元素y所在的集合合并,要求x和y所在的集合不相交,如果相交则不合并find(x)
:找到元素x所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了find(D)和find(F)分别返回元素D和元素F所属集合的代表A和H:
union(D, F)将元素D和元素F所在的集合合并:
public class UnionFind { int[] parents; public UnionFind(int totalNodes) { parents = new int[totalNodes]; for (int i = 0; i < totalNodes; i++) { parents[i] = i; } } public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP != rootQ) { parents[rootQ] = rootP; } } public int find(int p) { if (p == parents[p]) return p; return find(parents[p]); } public boolean isConnected(int p, int q) { return find(p) == find(q); } }
以上是并查集最基础的表示方法,如果创建的树严重不平衡就会退化成链表,可以用按秩合并和路径压缩两种办法来进行优化
按秩合并是指总是将更小的树连接至更大的树上。因为影响执行效率的是树的秩(深度),更小的树添加到更深的树的根上将不会增加秩除非它们的秩相同。当两棵秩同为r的树联合时,它们的秩r+1
对于一个树来说,它的根节点下面可以依附着许多的节点,因此,可以尝试在find的过程中,从底向上,如果此时访问的节点不是根节点的话,那么可以把这个节点尽量的往上挪一挪,减少数的层数,这个过程就叫做路径压缩
实现代码如下:
//在路径上的每个元素都直接指向根元素
public int find(int p) {
if (p == parents[p])
return p;
parents[p] = find(parents[p]);
return parents[p];
}
假设起始的并查集如上图所示,现在执行find(4)
,首先根据parents[4]
可以得出,4并不是一个根节点,因此,可以在向上继续查询之前,把这个节点往上面挪一挪(路径压缩),首先现在4节点连接到其父亲3节点上,我们可以让4节点不在指向3节点作为父亲节点了,而是让其跳一下,让其指向2节点(即父亲节点的父亲节点)作为新的父亲节点(如果该元素的父亲节点正好是根节点,那么让其指向父亲节点的父亲节点并不会出错,因为根据根元素的父亲节点指向其自己的结构,此时父亲节点的父亲节点仍然是有效的,即还是根节点,不会发生越界问题)
这样,树的层数由原来的5层变成了现在的4层,即路径被压缩了一下
下面,把继续来对2节点进行find操作,这里没有再去访问3节点,相当于跳过了一步操作(因为3节点也不是根节点,并不是我们想要返回的结果。如果3节点是根节点的话,那么4节点就会指向3节点,接下来就会访问3节点,所以这样的跳过是可行的),对于2节点来说,2节点也不是我们所要找到的根节点,因此,同样也可以对其进行压缩操作,让2节点指向父亲节点的父亲节点0节点作为新的父亲节点,如下图所示:
此时,树的层数由4层被压缩到了3层,与此同时,还跳过了一个1节点,接下来,只需要对0节点在进行路径压缩操作就好了。因为0节点是我们要找的根节点,因此,我们不再需要执行路径压缩操作了,只需要把找到的结果即根节点给返回就好了
实现代码如下:
public int find(int p) {
//如果p元素指向的不是自己,说明p并不是集合的根元素,还需要一直向上查找和路径压缩
while (parents[p] != p) {
//p元素不再指向原来的父亲节点,而是直接指向父亲节点的父亲节点来做为自己新的一个父亲节点,这样的操作使得树的层数被压缩了
parents[p] = parents[parents[p]];
//p压缩完毕后且p不是根节点,p变成p新的父节点继续进行查找和压缩的操作
p = parents[p];
}
return p;
}
给定一个由1
(陆地)和0
(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。可以假设网格的四个边均被水包围
示例1:
输入:
11110
11010
11000
00000
输出: 1
示例2:
输入:
11000
11000
00100
00011
输出: 3
题解:
int rows; int cols; public int numIslands(char[][] grid) { if (grid == null || grid.length == 0) return 0; rows = grid.length; cols = grid[0].length; UnionFind uf = new UnionFind(rows * cols + 1); int dummyNode = rows * cols; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { if (grid[i][j] == '0') uf.union(node(i, j), dummyNode); else { if (i + 1 < rows && grid[i + 1][j] == '1') uf.union(node(i + 1, j), node(i, j)); if (j + 1 < cols && grid[i][j + 1] == '1') uf.union(node(i, j + 1), node(i, j)); } } } return uf.getCount() - 1; } public int node(int i, int j) { return cols * i + j; } class UnionFind { private int[] parents; private int count; public UnionFind(int totalNodes) { parents = new int[totalNodes]; count = totalNodes; for (int i = 0; i < totalNodes; i++) { parents[i] = i; } } public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; parents[rootQ] = rootP; count--; } public int find(int p) { if (p == parents[p]) return p; parents[p] = find(parents[p]); return parents[p]; } public int getCount() { return count; } }
班上有N名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知A是B的朋友,B是C的朋友,那么我们可以认为A也是C的朋友。所谓的朋友圈,是指所有朋友的集合
给定一个N * N
的矩阵M,表示班级中学生之间的朋友关系。如果M[i][j] = 1
,表示已知第i个和j个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数
示例1:
输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。第2个学生自己在一个朋友圈。所以返回2
示例2:
输入:
[[1,1,0],
[1,1,1],
[0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1
注意:
N 在[1,200]的范围内
对于所有学生,有M[i][i] = 1
如果有M[i][j] = 1,则有M[j][i] = 1
题解:
public int findCircleNum(int[][] M) { int[] parent = new int[M.length]; Arrays.fill(parent, -1); for (int i = 0; i < M.length; i++) { for (int j = 0; j < M.length; j++) { if (M[i][j] == 1 && i != j) { union(parent, i, j); } } } int count = 0; for (int i = 0; i < parent.length; i++) { if (parent[i] == -1) count++; } return count; } public int find(int parent[], int i) { if (parent[i] == -1) return i; return find(parent, parent[i]); } public void union(int parent[], int p, int q) { int rootP = find(parent, p); int rootQ = find(parent, q); if (rootP != rootQ) parent[rootP] = rootQ; }
给定一个二维的矩阵,包含X
和O
(字母O)
找到所有被X
围绕的区域,并将这些区域里所有的O
用X
填充
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的O
都不会被填充为X
。任何不在边界上,或不与边界上的O
相连的O
最终都会被填充为X
。如果两个元素在水平或垂直方向相邻,则称它们是相连的
题解:
int rows; int cols; public void solve(char[][] board) { if (board == null || board.length == 0) return; rows = board.length; cols = board[0].length; //多申请一个空间 UnionFind uf = new UnionFind(rows * cols + 1); //所有边界的O节点都和dummy节点合并 int dummyNode = rows * cols; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { if (board[i][j] != 'O') continue; //当前节点在边界就和dummy合并 if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1) uf.union(node(i, j), dummyNode); else { //将上下左右的O节点和当前节点合并 if (board[i - 1][j] == 'O') uf.union(node(i, j), node(i - 1, j)); if (board[i + 1][j] == 'O') uf.union(node(i, j), node(i + 1, j)); if (board[i][j - 1] == 'O') uf.union(node(i, j), node(i, j - 1)); if (board[i][j + 1] == 'O') uf.union(node(i, j), node(i, j + 1)); } } } for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { //判断是否和dummy节点是一类 if (uf.isConnected(dummyNode, node(i, j))) board[i][j] = 'O'; else board[i][j] = 'X'; } } } public int node(int i, int j) { return cols * i + j; } class UnionFind { int[] parents; public UnionFind(int totalNodes) { parents = new int[totalNodes]; for (int i = 0; i < totalNodes; i++) { parents[i] = i; } } public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP != rootQ) { parents[rootQ] = rootP; } } public int find(int p) { if (p == parents[p]) return p; parents[p] = find(parents[p]); return parents[p]; } public boolean isConnected(int p, int q) { return find(p) == find(q); } }
参考:
https://www.cnblogs.com/MrSaver/p/9607552.html
https://blog.csdn.net/qq_19782019/article/details/78919990
常用数据结构的时间、空间复杂度:
https://www.bigocheatsheet.com/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。