介绍
并查集算法一般用于解决最小生成树问题,连通性问题,寻找子树问题,判断一个集合里是否存在子集。
部分能用并查集解决的一般都可以用DFS,BFS解决,并查集思想也与这二者类似
并查集的大致过程:不断构建子树并进行合并的过程
并查集三步曲
- 初始化,将每个元素的祖先设置为自己
int fa[MAXN]
- 查询
找到i的祖先直接返回,未进行路径压缩
int find(int i){
if(fa[i]==i){
return i;
}else{
return find(fa[i])
}
路径压缩版:返回时,将出口返回的父亲,全部设置为递归路径所经过节点的祖先
int find(int i){
if(fa[i]==i){
return i;
}else{
fa[i]=find(fa[i])
return fa[i];
}
- 合并,将祖先合并
注意:
if(i_fa!=j_fa){
fa[i_fa]=j_fa;//将i的祖先指向j的祖先
}
注意这里是为了避免重复合并
void union(int i,int j){
int i_fa=find(i);
int j_fa=find(j);
if(i_fa!=j_fa){
fa[i_fa]=j_fa;//将i的祖先指向j的祖先
}
}
一开始每个元素都指向自己,也就相当于初始化操作
两两互相合并后的集合,可以与不同的其他集合合并
java定义一个类做为并查集模板
class UnionFind{
private int[]father;//创建祖先数组,并将每个元素的祖先设置为自己
private int count=0;
private int []father;
public int getCount(){
this.count=count;
return count;
}
//查寻各自的祖先
public int find(int i){
if (father[i]==i){
return i;
}
father[i]=find(father[i]);//???
return father[i];
}
//集(合并操作)
public void union(int i,int j){
int ifather=find(i);
int jfather=find(j);
if (ifather!=jfather){//避免重复和并
father[ifather]=jfather;
count++;//合并次数
}
}
}
例题
岛屿数量
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
[“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”]
]
输出:1
示例 2:
输入:grid = [
[“1”,“1”,“0”,“0”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“1”,“0”,“0”],
[“0”,“0”,“0”,“1”,“1”]
]
输出:3
思路
观察到连通的一片1就可以视为一座岛屿,使用并查集,遍历岛屿以其中一个1作为中心,将四周为1的元素合并,并记录合并次数
岛屿的数量=总元素-水域-合并的次数
代码
package practise.第十二届模拟赛;
import java.util.Scanner;
public class 岛屿数量 {
private static int water=0;
private static int unionnnum;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n=scanner.nextInt();
int m=scanner.nextInt();//n行m列的岛屿
int[][] grid = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
grid[i][j]=scanner.nextInt();
}
}
int num=thenumofland(grid);
System.out.println(num);
}
public static int thenumofland(int[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int row=grid.length;
int low=grid[0].length;
UnionFind unionFind = new UnionFind(grid);
for (int i = 0; i < row; i++) {
for (int j = 0; j < low; j++) {
if (grid[i][j]==0){
water++;
}else {
int[][]dir ={{0,1},{1,0},{-1,0},{0,-1}};
for (int[]direction:dir) {//将dir中的元素,依次赋给direction并更新
int x=i+direction[0];
int y=j+direction[1];
if (x>=0&&y>=0&&x<row&&y<low&&grid[x][y]==1){//边界条件出错
unionFind.union(low*i+j,low*x+y);//和并情况判断出错
}
}
}
}
}
return row*low-water-unionFind.getCount();
}
}
class UnionFind{
private int[]father;//创建祖先数组,并将每个元素的祖先设置为自己
private int count=0;
public int getCount(){
this.count=count;
return count;
}
public UnionFind(int [][] grid){//构造器,将二维数组对应数改为father中元素
father=new int[grid.length* grid[0].length];
for (int i=0;i<grid.length* grid[0].length;i++){
father[i]=i;//初始化
}
}
//查寻各自的祖先
public int find(int i){
if (father[i]==i){
return i;
}
father[i]=find(father[i]);
return father[i];
}
//集(合并操作)
public void union(int i,int j){
int ifather=find(i);
int jfather=find(j);
if (ifather!=jfather){//避免重复和并
father[ifather]=jfather;
count++;
}
}
}