并查集练习 — 岛屿问题(解法二)

news/2025/2/9 8:41:30/

题目如岛屿问题解法一文章所介绍,这里不过多赘述,直接讲解第二种解法。

并查集解法

并查集解法的整体思路是,将二维数组中为‘1’的部分提取出来作为样本,再进行判断,如果左上方向有同样为‘1’的,则进行合并,最后看一共有几座岛屿。
不过并查集解法有一个问题,给定的二维数组中是char类型,Java中基本数据都行都会作为值进行传递,那么该怎么分辨二维数组中所有的’1’是不相同的呢?

代码
比较粗糙的做法是用一个类替代这个‘1’,声明一个和参数board等长的Dot类的二维数组,从头至尾遍历一次,将board中为1的位置在Dot数组中对应位置创建Dot对象。
将Dot作为并查集中的样本。利用对象的内存地址不同来区别出不同的1,遍历,进行union后,返回集合个数。
代码主流程中,在进行union时一共进行了三次的遍历。
第一次遍历:只遍历第0列,因为第0列左侧没有值,所以不用考虑左侧。
第二次遍历:只遍历第0行,因为第0行上面没有值,所以不用考虑上面。
第三次遍历:从第1行第1列遍历。只考虑左上方向有没有‘1’,有‘1’则进行合并即可,不用再考虑边界问题。
如果用1个for循环搞定这些,可以。不过常数时间更复杂。每次都要判断边界。

 public static class Node<V> {V val;public Node(V value) {this.val = value;}}public static class Dot {}public static class UnionFind1<V> {HashMap<V, Node<V>> nodes;HashMap<Node<V>, Node<V>> parentMap;HashMap<Node<V>, Integer> sizeMap;public UnionFind1(List<V> lists) {nodes = new HashMap<>();parentMap = new HashMap<>();sizeMap = new HashMap<>();for (V val : lists) {Node node = new Node(val);nodes.put(val, node);parentMap.put(node, node);sizeMap.put(node, 1);}}public Node<V> findParent(Node<V> node) {Stack<Node> stack = new Stack<>();while (node != parentMap.get(node)) {node = parentMap.get(node);stack.push(node);}while (!stack.isEmpty()) {parentMap.put(stack.pop(), node);}return node;}public void union(V a, V b) {Node aNode = findParent(nodes.get(a));Node bNode = findParent(nodes.get(b));if (aNode != bNode) {int aSize = sizeMap.get(aNode);int bSize = sizeMap.get(bNode);Node big = aSize >= bSize ? aNode : bNode;Node small = big == aNode ? bNode : aNode;parentMap.put(small, big);sizeMap.put(big, aSize + bSize);sizeMap.remove(small);}}public int getSize() {return sizeMap.size();}}public static int numIslands2(char[][] board) {int row = board.length;int col = board[0].length;Dot[][] dots = new Dot[row][col];List<Dot> dotList = new ArrayList<>();for (int i = 0; i < row; i++) {for (int j = 0; j < col; j++) {if (board[i][j] == '1') {dots[i][j] = new Dot();dotList.add(dots[i][j]);}}}UnionFind1<Dot> unionFind1 = new UnionFind1<>(dotList);for (int i = 1; i < col; i++) {if (board[0][i - 1] == '1' && board[0][i] == '1') {unionFind1.union(dots[0][i - 1], dots[0][i]);}}for (int i = 1; i < row; i++) {if (board[i - 1][0] == '1' && board[i][0] == '1') {unionFind1.union(dots[i - 1][0], dots[i][0]);}}for (int i = 1; i < row; i++) {for (int j = 1; j < col; j++) {if (board[i][j] == '1') {if (board[i][j - 1] == '1') {unionFind1.union(dots[i][j], dots[i][j - 1]);}if (board[i - 1][j] == '1') {unionFind1.union(dots[i][j], dots[i - 1][j]);}}}}return unionFind1.getSize();}

优化

上面的Dot类很粗糙,为了区别 '1’声明了一个类,很臃肿,所以还可以将上面的方法进行优化。
因为传进来的是一个二维数组,优化的过程是将二维数组拉伸,变成一个一维数组。
根据二维数组的列和行,算出新数组整体的长度,并放到新数组中。
比如说下图:3行4列的数组
在这里插入图片描述
新数组长度 row * col = 12,放入新数组的规则是: 当前行数 * 总列数 + 当前列数,第0行放入新数组中就是数组下标 0 ~ 3,第1行0列的1,放入新数组中就是 1 * 4 + 0 = 4,以此类推。
依然是将二维数组中‘1’部分当做是样本放到并查集中。相同就合并(在新数组中),最后再查看数组中集合个数即可。

代码

 public static int numIslands3(char[][] board) {int row = board.length;int col = board[0].length;UnionFind2 unionFind2 = new UnionFind2(board);for (int i = 1; i < row; i++) {if (board[i - 1][0] == '1' && board[i][0] == '1') {unionFind2.union(i - 1, 0, i, 0);}}for (int j = 1; j < col; j++) {if (board[0][j - 1] == '1' && board[0][j] == '1') {unionFind2.union(0, j - 1, 0, j);}}for (int i = 1; i < row; i++) {for (int j = 1; j < col; j++) {if (board[i][j] == '1') {if (board[i][j - 1] == '1') {unionFind2.union(i, j, i, j - 1);}if (board[i - 1][j] == '1') {unionFind2.union(i, j, i - 1, j);}}}}return unionFind2.getSets();}public static class UnionFind2 {int[] parent;int[] size;int[] help;int col;int sets;public UnionFind2(char[][] board) {int row = board.length;this.col = board[0].length;int len = row * col;parent = new int[len];help = new int[len];size = new int[len];sets = 0;for (int i = 0; i < row; i++) {for (int j = 0; j < col; j++) {if (board[i][j] == '1') {int index = index(i, j);parent[index] = index;size[index] = 1;sets++;}}}}public int index(int row, int currCol) {return row * col + currCol;}// index -> 计算后在parent中的下标;public int findParent(int index) {int num = 0;while (index != parent[index]) {index = parent[index];help[num++] = index;}for (int i = 0; i < num; i++) {parent[help[i]] = index;}return index;}public void union(int r1, int c1, int r2, int c2) {int i1 = index(r1, c1);int i2 = index(r2, c2);int f1 = findParent(i1);int f2 = findParent(i2);if (f1 != f2) {if (size[f1] >= size[f2]) {size[f1] += size[f2];parent[f2] = f1;} else {size[f2] += size[f1];parent[f1] = f2;}sets--;}}public int getSets() {return sets;}}

http://www.ppmy.cn/news/1009811.html

相关文章

web基础与HTTP协议基础

目录 DNS域名 1.1、DNS解析的方式&#xff1a; 1.2、生效顺序 1.3、域名解析服务器作用&#xff1a; 1.4、解析端口&#xff08;客户端&#xff09; 1.5、注册域名&#xff08;了解即可&#xff09; 2、HTML 2.1、现在网页通用的页面标记语言 2.2、网页、网站的要素组…

MySQL(1)

MySQL创建数据库和创建数据表 创建数据库 1. 连接 MySQL mysql -u root -p 2. 查看当前的数据库 show databases; 3. 创建数据库 create database 数据库名; 创建数据库 4. 创建数据库时设置字符编码 create database 数据库名 character set utf8; 5. 查看和显示…

一百四十三、Linux——Linux的CentOS 7系统语言由中文改成英文

一、目的 之前安装CentOS 7系统的时候把语言设置成中文&#xff0c;结果Linux文件夹命名出现中文乱码的问题&#xff0c;于是决定把Linux系统语言由中文改成英文 二、实施步骤 &#xff08;一&#xff09;到etc目录下&#xff0c;找到配置文件locale.conf # cd /etc/ # ls…

openGauss学习笔记-31 openGauss 高级数据管理-索引

文章目录 openGauss学习笔记-31 openGauss 高级数据管理-索引31.1 语法格式31.2 参数说明31.3 示例 openGauss学习笔记-31 openGauss 高级数据管理-索引 索引是一个指向表中数据的指针。一个数据库中的索引与一本书的索引目录是非常相似的。 索引可以用来提高数据库查询性能&…

《大型网站技术架构设计》第二篇 架构-高可用

高可用在公司中的重要性 对公司而言&#xff0c;可用性关系网站的生死存亡。对个人而言&#xff0c;可用性关系到自己的绩效升迁。 工程师对架构做了许多优化、对代码做了很多重构&#xff0c;对性能、扩展性、伸缩性做了很多改善&#xff0c;但别人未必能直观地感受到&#…

STM32CubeMx学习FreeRTOS的绝对延时和相对延时

在阻塞状态中 可以空闲出时间 来让低优先级的任务可以进行 有两种阻塞延时 一个是相对延时 也就是 osDelay(500); 这样的osDelay可以让在到这里的时候&#xff0c;延时500ms 也就是程序到这里才500ms 不记程序前面所用的时间 而还有一个绝对延时 vTaskDelayUntil(&x…

【云原生】Docker-compose中所有模块学习

compose模块 模板文件是使用 Compose 的核心&#xff0c;涉及到的指令关键字也比较多。但大家不用担心&#xff0c;这里面大部分指令跟 docker run 相关参数的含义都是类似的。 默认的模板文件名称为 docker-compose.yml&#xff0c;格式为 YAML 格式。 version: "3&quo…

Spring Data JPA源码

导读: 什么是Spring Data JPA? 要解释这个问题,我们先将Spring Data JPA拆成两个部分&#xff0c;即Sping Data和JPA。 从这两个部分来解释。 Spring Data是什么? 摘自: https://spring.io/projects/spring-data Spring Data’s mission is to provide a familiar and cons…