并查集应用

本文介绍了并查集的概念,包括初始化、查找根节点和合并集合操作。通过并查集解决LeetCode上的岛屿数量问题,实现了一个Java解决方案,用于计算二维网格中陆地区域(1表示陆地,0表示水)的数量。通过遍历和合并相邻的陆地,最终计算出岛屿的个数。并查集在此场景中用于判断两个集合是否相交,从而有效解决岛屿计数的问题。
摘要由CSDN通过智能技术生成

并查集

简单回顾下并查集概念的几个概念

  1. 初始化,makeSet,为每个元素构建一个集合
  2. find,查找集合根节点
  3. union,合并集合

详情任意门:https://blog.csdn.net/u010597819/article/details/104600289

并查集的应用场景

  1. 判断两个集合是否存在交集
  2. 最小生成树

应用

岛屿数量

题目

给你一个由 ‘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中有很多更优秀的代码实现可以学习。文章内容基于博主的理解写的代码实现,加深理解与记忆,有一些性能问题以及看起来别扭的代码可以选择性略过_

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值