并查集应用
并查集一般可以解决一下问题 :
1.查找元素属于哪个集合
沿着数组表示树形关系以上一直找到根(即:树中中元素为负数的位置)
2.查看两个元素是否属于同一个集合
沿着数组表示的树形关系往上一直找到树的根,如果根相同表明在同一个集合,否则不在
3.将两个集合归并成一个集合
(1)将两个集合中的元素合并
(2)将一个集合名称改成另一个集合的名称
4.集合的个数
遍历数组,数组中元素为负数的个数即为集合的个数。
原理学习: 算法学习笔记(1) : 并查集 - 知乎 (zhihu.com)
并查集常用模板:
class UnionFind{
int[] parent;// parent[i]表示i这个元素指向的父亲节点
int[] size;//size[i]表示以i为根节点的集合中元素个数
int n;//节点的个数,初始化每一个节点都是一个单独的连通分量
int setCount;//连通分量的数目
public UnionFind(int n){
this.size=new int[n];
this.parent=new int[n];
this.n=n;
this.setCount=n;
Arrays.fill(size,1);
for(int i=0;i<n;i++){
parent[i]=i;
}
}
public int find(int x){
return parent[x]==x?x:find(parent[x]);
}
public boolean unit(int x,int y){
x=find(x);
y=find(y);
if(x==y){
return false;
}
if(size[x]<size[y]){
int tem=x;
x=y;
y=tem;
}
parent[y]=x;
size[x]+=size[y];
--setCount;
return true;
}
public boolean connected(int x, int y) {
x = find(x);
y = find(y);
return x == y;
}
}
示例:
e.g.1
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
示例 1:
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]] 输出:2
示例2:
输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]] 输出:3
给定的矩阵 isConnected isConnected 即为图的邻接矩阵,省份即为图中的连通分量。
也可以dfs,bfs写
class Solution {
public int findCircleNum(int[][] isConnected) {
UnionFind uf = new UnionFind(isConnected);
int len1 = isConnected.length;
int len2 = isConnected[0].length;
for(int i = 0 ; i < len1 ; i++){
for(int j = 0 ; j < len2 ; j++){
if(isConnected[i][j] == 1){
uf.union(i,j);
}
}
}
return uf.sum;
}
}
//并查集及其常用方法
class UnionFind{
int sum;
int[] size;
int[] parent;
//初始化并查集
UnionFind (int[][] isConnected){
int len = isConnected.length;
this.sum = len;//初始化长度
this.size =new int[len];
Arrays.fill(size,1);//用1填满数组
this.parent =new int[len];
for(int i = 0 ;i<len;i++){
parent[i] = i;
}
}
//获得省的省会
public int getHead(int x){
while(x != parent[x]){
x = parent[x];
}
return x;
}
//俩个城市合并
public void union(int x,int y){
int xHead = getHead(x);
int yHead = getHead(y);
if(xHead == yHead){
//说明是同一个组合,不用联合
return;
}
if(xHead>yHead){
parent[yHead] = xHead;
size[xHead] += size[yHead];
sum--;
}else{//xHead < yHead
parent[xHead] = yHead;
size[yHead] += size[xHead];
sum--;
}
}
}
e.g.2
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
示例 1:
输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
示例 2:
输入:grid = [[0,0,0,0,0,0,0,0]]
输出:0
class Solution {
public int maxAreaOfIsland(int[][] grid) {
UnionFind uf = new UnionFind(grid);
int len1 = grid.length,len2 = grid[0].length;
for(int i = 0 ; i < len1 ;i++){
for(int j = 0 ; j < len2 ;j++){
if(grid[i][j] == 0) continue;
if(i-1 >=0 && grid[i-1][j] == 1){
uf.union(i*len2+j,(i-1)*len2+j);
}
if(j-1 >=0 && grid[i][j-1] == 1){
uf.union(i*len2+j,i*len2+j-1);
}
if(i+1 < len1 && grid[i+1][j] == 1){
uf.union(i*len2+j,(i+1)*len2+j);
}
if(j+1 < len2 && grid[i][j+1] == 1){
uf.union(i*len2+j,i*len2+j+1);
}
}
}
int max = 0;
for(int i = 0 ; i < uf.sum ; i++){
if(uf.size[i] > max){
max = uf.size[i];
}
}
return max;
}
}
class UnionFind{
int sum;
int[] size;
int[] parent;
UnionFind(int[][] grid){
int len1 = grid.length,len2 = grid[0].length;
this.sum = len1*len2;
this.size = new int[sum];
this.parent = new int[sum];
for(int i = 0 ; i < len1 ; i++){
for(int j = 0 ; j < len2 ; j++){
parent[i*len2+j] = i*len2+j;//每行有len2个数
if(grid[i][j] == 1){
size[i*len2 + j] = 1;
}else{
size[i*len2 + j] = 0;
}
}
}
}
public int getHead(int x){
//传进来的x即为i*len2 + j
while(x != parent[x]){
x = parent[x];
}
return x;
}
public void union(int x,int y){
int xHead = getHead(x);
int yHead = getHead(y);
if(xHead == yHead) return;//俩个已经在同一座岛屿上、
if(xHead > yHead){
size[xHead] += size[yHead];
parent[yHead] = xHead;
}else{
size[yHead] += size[xHead];
parent[xHead] = yHead;
}
}
}
e.g.3
200. 岛屿数量 - 力扣(LeetCode)
给你一个由 '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
class Solution {
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
UnionFind uf = new UnionFind(grid);
int len1 = grid.length,len2 = grid[0].length;
for(int i = 0 ; i < len1 ; i++){
for(int j = 0; j < len2 ; j++){
if(grid[i][j] == '0') {
continue;
}
grid[i][j] = '0';
if(i-1 >=0 && grid[i-1][j] == '1'){
uf.union(i*len2+j,(i-1)*len2+j);
}
if(j-1 >=0 && grid[i][j-1] == '1'){
uf.union(i*len2+j,i*len2+j-1);
}
if(i+1 < len1 && grid[i+1][j] == '1'){
uf.union(i*len2+j,(i+1)*len2+j);
}
if(j+1 < len2 && grid[i][j+1] == '1'){
uf.union(i*len2+j,i*len2+j+1);
}
}
}
return uf.getCount();
}
}
class UnionFind{
int sum;
int count;
int[] size;
int[] parent;
UnionFind(char[][] grid){
int len1 = grid.length,len2 = grid[0].length;
count =0;
this.sum = len1*len2;
this.size = new int[sum];
this.parent = new int[sum];
for(int i = 0 ; i < len1 ; i++){
for(int j = 0 ; j < len2 ; j++){
size[i*len2 + j] = 0;
if(grid[i][j] == '1'){
++count;
parent[i*len2+j] = i*len2+j;
//每行有len2个数
}
}
}
}
public int getCount(){
return count;
}
public int getHead(int x){
//传进来的x即为i*len2 + j
while(x != parent[x]){
x = parent[x];
}
return x;
}
public void union(int x,int y){
int rootx = getHead(x);
int rooty = getHead(y);
if (rootx != rooty) {
if (size[rootx] > size[rooty]) {
parent[rooty] = rootx;
} else if (size[rootx] < size[rooty]) {
parent[rootx] = rooty;
} else {
parent[rooty] = rootx;
size[rootx] += 1;
}
--count;
}
}
}
感觉并查集像是套模板一样,不太会用hhhh