概念:
检查两个元素是否属于同一个集合.
两个元素所属集合合并成同一个集合.
设元素A
属于集合 set1
。
元素B
属于集合set2
.
直观思路:用list
结构分别装载两个集合,则有 list1
对应集合set1
, list2
对应集合set2
- 查看
list1
中是否存在B,即可判断A和B是否同属于一个集合。 - 合并:将
list1
和list2
连接起来
缺点:无法做到迅速查找和迅速合并.
采用树状结构表示集合,选择集合中的一个元素作为根节点,同时根节点指向自己,其他节点作为它的后代节点. 根节点成为整个集合的代表.
- 查:看两个集合的代表是否是同一个节点(集合代表指向自己)
- 并:节点少的集合,其集合代表指向节点多的集合的代表.
进一步优化:
查找某个元素的集合代表过程中,访问过的节点直接指向集合代表.
【map实现(初级7)】:
fatherMap
: key: child
, value: father
sizeMap
: 某一个节点(集合代表)所在集合个数
单次查询次数和合并次数:O(1)
leetcode 200. 岛屿数量
给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入:
11000
11000
00100
00011
输出: 3
题解:
- 思路一:深度优先搜索遍历(DFS)
遍历每一个元素:当前元素若为’1’, 置0. 递归地查看上下左右是否有’1’, 有’1’将其置0.
tips: 二维数组判空
- 首地址为空:
grid==null
- {} :
grid.length ==0
- {{}}:
grid.length==1 && grid[0].lengh==0
JAVA代码
class Solution {
public int numIslands(char[][] grid) {
// 二维数组判空
//1. 首地址为空: grid==null
//2. {} : grid.length ==0;
//3. {{}}: grid.length==1 grid[0].length==0
if (grid == null || grid.length == 0) return 0;
int num = 0;
int M = grid.length; //行
int N = grid[0].length; //列
for (int i = 0; i < M; i++){
for (int j=0; j < N; j++){
if (grid[i][j]=='1'){
num ++;
//以grid[i][j]为源头找属于同一个岛的元素,然后置0
infect(grid, i, j, M, N);
}
}
}
return num;
}
public void infect(char[][] grid, int i, int j, int M, int N){
if (j<0 || j>=N || i<0 || i>=M || grid[i][j]=='0') //不能写成grid[i][j]=='0'
return;
grid[i][j]='0';
infect(grid, i, j-1, M, N);
infect(grid, i, j+1, M, N);
infect(grid, i-1, j, M, N);
infect(grid, i+1, j, M, N);
}
}
-
思路二:广度优先搜索遍历(BFS)
借用队列 -
思路三:并查集
属于同一个岛的元素合并为同一个集合。
岛的数量即为集合的数量(找集合代表)
遍历的时候只需要查看右和下的元素。
JAVA代码
class Solution {
public int numIslands(char[][] grid) {
// 二维数组判空
//1. 首地址为空: grid==null
//2. {} : grid.length ==0;
//3. {{}}: grid.length==1 grid[0].length==0
if (grid == null || grid.length == 0) return 0;
int M = grid.length; //行
int N = grid[0].length; //列
//初始化father数组,默认集合代表指向-1
int[] father = new int[M * N];
Arrays.fill(father, -1);
for (int i = 0; i < M; i++){
for (int j=0; j < N; j++){
if (grid[i][j]=='1'){
grid[i][j] = '0';
//下方
if (i+1<M && grid[i+1][j]=='1'){
union(father, i*N+j, (i+1)*N+j);
}
//右边
if (j+1<N && grid[i][j+1]=='1'){
union(father, i*N+j, i*N+j+1);
}
}
else{
father[i*N + j] = -2;
}
}
}
int count = 0;
for (int num:father){
if (num==-1)
count++;
}
return count;
}
// 找集合代表
public int find(int[] father, int i){
if (father[i] == -1) return i; //集合代表为-1
else{
// 查找过程路径压缩
father[i] = find(father,father[i]); // 父节点设为根节点。
return father[i]; // 返回父节点
}
}
// 合并
public void union(int[] father, int i, int j){
if (find(father,i) != find(father,j))
father[find(father,i)] = find(father,j);
}
}
扩展:如何分布式并行处理
直观思路:将二维网络切分成若干份,每一份予一台CPU中.
求出每台CPU中岛屿数量,再合并边界信息。
边界处理方式:并查集思想
- 并行计算岛屿数量,将同属于一个岛屿的元素合并为同一个集合。得到岛屿 ”总数量“
- 处理边界时,边界处两边都有岛屿,且不属于同一集合,则将两个集合合并,岛屿数量相应减1.