并查集
简单回顾下并查集概念的几个概念
- 初始化,makeSet,为每个元素构建一个集合
- find,查找集合根节点
- union,合并集合
详情任意门:https://blog.csdn.net/u010597819/article/details/104600289
并查集的应用场景
- 判断两个集合是否存在交集
- 最小生成树
应用
岛屿数量
题目
给你一个由 ‘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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-islands
实现
岛屿数量
package org.gallant.leetcode.algorithms;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.gallant.leetcode.algorithms.domain.UnionSetNode;
/**
* @author : 会灰翔的灰机
* @date : 2021/8/22
*/
public class Q200NumIslands {
private static Map<String, Set<UnionSetNode>> unionSet = new HashMap<>();
private static final String UNION_SET_NODE_KEY = "x:%s,y:%s";
/**
* 初始化并查集
* @param data : 输入数据集合
*/
private static void makeSet(int[][] data) {
unionSet = new HashMap<>(data.length + data[0].length);
for (int i = 0; i < data.length; i++) {
int[] row = data[i];
for (int i1 = 0; i1 < row.length; i1++) {
int value = row[i1];
if (value == 1) {
String unionSetKey = getUnionSetNodeKey(i1, i);
unionSet.putIfAbsent(unionSetKey, new HashSet<>());
Set<UnionSetNode> unionSetNodes = unionSet.get(unionSetKey);
UnionSetNode unionSetNode = new UnionSetNode();
unionSetNode.setY(i);
unionSetNode.setX(i1);
unionSetNode.setValue(value);
unionSetNodes.add(unionSetNode);
}
}
}
}
/**
* 查找根节点
* @param unionSetNode : 输入节点
* @return : 根节点
*/
private static UnionSetNode find(UnionSetNode unionSetNode) {
if (unionSetNode.getParent() == null) {
return unionSetNode;
}
return find(unionSetNode.getParent());
}
/**
* 合并并查集:node2合并至node1节点
* @param node1 :
* @param node2 :
*/
private static void union(UnionSetNode node1, UnionSetNode node2) {
UnionSetNode root1 = find(node1);
UnionSetNode root2 = find(node2);
if (root1 != root2) {
root2.setParent(root1);
}
}
/**
* 岛屿数量
* @param data : 数据集合
* @return : 岛屿数量
*/
private static int numIslands(int[][] data) {
makeSet(data);
unionSet.forEach((k, v) -> {
UnionSetNode currentRoot = find(v.iterator().next());
int x = currentRoot.getX();
int y = currentRoot.getY();
int leftX = x - 1;
Set<UnionSetNode> leftNodes = unionSet.get(getUnionSetNodeKey(leftX, y));
// 如果存在左邻的陆地,则合并
if (leftNodes != null) {
UnionSetNode leftRoot = find(leftNodes.iterator().next());
union(leftRoot, currentRoot);
}
// 如果存在右邻的陆地,则合并
int rightX = x + 1;
Set<UnionSetNode> rightNodes = unionSet.get(getUnionSetNodeKey(rightX, y));
if (rightNodes != null) {
UnionSetNode rightRoot = find(rightNodes.iterator().next());
union(currentRoot, rightRoot);
}
// 如果存在上邻的陆地,则合并
int upY = y - 1;
Set<UnionSetNode> upNodes = unionSet.get(getUnionSetNodeKey(x, upY));
if (upNodes != null) {
UnionSetNode upRoot = find(upNodes.iterator().next());
union(upRoot, currentRoot);
}
// 如果存在下邻的陆地,则合并
int downY = y + 1;
Set<UnionSetNode> downNodes = unionSet.get(getUnionSetNodeKey(x, downY));
if (downNodes != null) {
UnionSetNode downRoot = find(downNodes.iterator().next());
union(currentRoot, downRoot);
}
});
Set<UnionSetNode> roots = new HashSet<>();
unionSet.forEach((k, v) -> roots.add(find(v.iterator().next())));
return roots.size();
}
public static void main(String[] args) {
String example1 = "[\n"
+ " [\"1\",\"1\",\"1\",\"1\",\"0\"],\n"
+ " [\"1\",\"1\",\"0\",\"1\",\"0\"],\n"
+ " [\"1\",\"1\",\"0\",\"0\",\"0\"],\n"
+ " [\"0\",\"0\",\"0\",\"0\",\"0\"]\n"
+ "]";
int[][] data1 = initData(example1);
printArray(data1);
// 所有陆地集合初始化
makeSet(data1);
printUnionSet();
// 岛屿数量
System.out.println(numIslands(data1));
printUnionSet();
System.out.println("---------------------");
String example2 = "[\n"
+ " [\"1\",\"1\",\"0\",\"0\",\"0\"],\n"
+ " [\"1\",\"1\",\"0\",\"0\",\"0\"],\n"
+ " [\"0\",\"0\",\"1\",\"0\",\"0\"],\n"
+ " [\"0\",\"0\",\"0\",\"1\",\"1\"]\n"
+ "]";
int[][] data2 = initData(example2);
printArray(data2);
// 所有陆地集合初始化
makeSet(data2);
// 岛屿数量
System.out.println(numIslands(data2));
}
private static String getUnionSetNodeKey(int x, int y) {
return String.format(UNION_SET_NODE_KEY, x, y);
}
private static int[][] initData(String dataStr) {
int[][] data = new int[4][5];
int i=0;
String[] line = dataStr.split("\n");
for (String s : line) {
if (s.length() == 1) {
continue;
}
String row = s.replaceAll("[\\[\\]]", "");
row = row.substring(0, row.length() - 1);
String[] cols = row.split(",");
int j=0;
for (String col : cols) {
data[i][j++] = Integer.parseInt(col.replace("\"", "").trim());
}
i++;
}
return data;
}
private static void printArray(int[][] data) {
for (int[] datum : data) {
for (int i : datum) {
System.out.print(i);
}
System.out.println();
}
}
private static void printUnionSet() {
unionSet.forEach((k, v) -> System.out.println(k + "," + v));
}
}
并查集节点
package org.gallant.leetcode.algorithms.domain;
/**
* @author : 会灰翔的灰机
* @date : 2021/8/22
*/
public class UnionSetNode {
/**
* 父节点
*/
private UnionSetNode parent;
/**
* 横坐标
*/
private int x;
/**
* 纵坐标
*/
private int y;
/**
* 节点值
*/
private int value;
public UnionSetNode getParent() {
return parent;
}
public void setParent(UnionSetNode parent) {
this.parent = parent;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
@Override
public int hashCode() {
return String.format("%s%s", x, y).hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof UnionSetNode)) {
return false;
}
UnionSetNode input = (UnionSetNode) obj;
return x == input.getX() && y == input.getY();
}
@Override
public String toString() {
return "UnionSetNode{" +
(parent == null ? null : "parent=" + parent.getX() + ":" + parent.getY()) +
", x=" + x +
", y=" + y +
", value=" + value +
'}';
}
}
总结
最小生成树逻辑不再撸代码了,有兴趣的同学可以试一试,实现原理是一致的,岛屿数量也好,朋友圈问题也好,均涉及集合交集问题,因此可以选择并查集实现解决,当然在leetcode中有很多更优秀的代码实现可以学习。文章内容基于博主的理解写的代码实现,加深理解与记忆,有一些性能问题以及看起来别扭的代码可以选择性略过_