代码随想录Day51:图论(岛屿数量 深搜广搜、岛屿的最大面积)
一、实战
99岛屿数量 深搜
99. 岛屿数量
本题中每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成,也就是说斜角度链接是不算的。思路是用遇到一个没有遍历过的节点陆地,计数器就加一,然后把该节点陆地所能遍历到的陆地都标记上。在遇到标记过的陆地节点和海洋节点的时候直接跳过。 这样计数器就是最终岛屿的数量。
深度优先搜索有两个版本,一个是没有终止条件,一个是有终止条件
没有终止条件版本:
package org.example;//具体运行时去掉import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scan = new Scanner(System.in);int m = scan.nextInt(); // 网格行数int n = scan.nextInt(); // 网格列数int[][] grid = new int[m][n]; // 存储网格数据// 输入网格for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {grid[i][j] = scan.nextInt();}}boolean[][] visited = new boolean[m][n]; // 标记是否已访问int result = 0; // 记录岛屿数量// 遍历每个格子for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {// 如果当前格子是陆地(1)且未被访问if (!visited[i][j] && grid[i][j] == 1) {result++; // 发现新岛屿visited[i][j] = true; // 标记为已访问dfs(visited, i, j, grid); // 深度优先搜索,标记整个连通区域}}}System.out.println(result); // 输出岛屿总数scan.close();}// 四个方向:右、下、上、左(x, y 增量)public static int[][] dir = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};/*** 从坐标 (x, y) 开始进行深度优先搜索,标记所有相连的陆地*/private static void dfs(boolean[][] visited, int x, int y, int[][] grid) {// 向四个方向扩展for (int i = 0; i < 4; i++) {int nextX = x + dir[i][0];int nextY = y + dir[i][1];// 越界检查if (nextX < 0 || nextY < 0 || nextX >= grid.length || nextY >= grid[0].length) {continue;}// 如果下一个位置是未访问的陆地if (!visited[nextX][nextY] && grid[nextX][nextY] == 1) {visited[nextX][nextY] = true; // 标记为已访问dfs(visited, nextX, nextY, grid); // 继续递归搜索}}}
}
有终止条件版本,其实终止条件 就写在了 调用dfs的地方,如果遇到不合法的方向,直接不会去调用dfs。:
package org.example;//具体运行时去掉import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scan = new Scanner(System.in);int m = scan.nextInt(); // 网格行数int n = scan.nextInt(); // 网格列数int[][] grid = new int[m][n]; // 存储网格数据// 输入网格for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {grid[i][j] = scan.nextInt();}}boolean[][] visited = new boolean[m][n]; // 标记是否已访问int result = 0; // 记录岛屿数量// 遍历每个格子for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {// 如果当前格子是陆地(1)且未被访问if (!visited[i][j] && grid[i][j] == 1) {result++; // 发现新岛屿visited[i][j] = true; // 标记为已访问dfs(visited, i, j, grid); // 深度优先搜索,标记整个连通区域}}}System.out.println(result); // 输出岛屿总数scan.close();}// 四个方向:右、下、上、左(x, y 增量)public static int[][] dir = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};/*** 从坐标 (x, y) 开始进行深度优先搜索,标记所有相连的陆地*/private static void dfs(boolean[][] visited, int x, int y, int[][] grid) {if (visited[x][y] || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水visited[x][y] = true; // 标记为已访问// 向四个方向扩展for (int i = 0; i < 4; i++) {int nextX = x + dir[i][0];int nextY = y + dir[i][1];// 越界检查if (nextX < 0 || nextY < 0 || nextX >= grid.length || nextY >= grid[0].length) {continue;}dfs(visited, nextX, nextY, grid); // 继续递归搜索}}
}
99岛屿数量 广搜
99. 岛屿数量
本题思路与深搜类似,遇到一个没有遍历过的节点陆地,计数器就加一,然后把该节点陆地所能遍历到的陆地都标记上。再遇到标记过的陆地节点和海洋节点的时候直接跳过。 这样计数器就是最终岛屿的数量。
用广搜做这道题目的时候容易超时,因为已经入队列的节点因为标记的时间太晚导致重复进入队列,因此只要 加入队列就代表 走过,就需要标记,而不是从队列拿出来的时候再去标记走过。

//超时写法
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {queue<pair<int, int>> que;que.push({x, y});// 应在放入队列的时候就进行标记while(!que.empty()) {pair<int ,int> cur = que.front(); que.pop();int curx = cur.first;int cury = cur.second;visited[curx][cury] = true; // 从队列中取出在标记走过for (int i = 0; i < 4; i++) {int nextx = curx + dir[i][0];int nexty = cury + dir[i][1];if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 越界了,直接跳过if (!visited[nextx][nexty] && grid[nextx][nexty] == '1') {que.push({nextx, nexty});}}}}
具体代码如下:
package org.example;//具体运行时去掉import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scan = new Scanner(System.in);int m = scan.nextInt(); // 网格行数int n = scan.nextInt(); // 网格列数int[][] grid = new int[m][n]; // 存储网格数据// 输入网格for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {grid[i][j] = scan.nextInt();}}boolean[][] visited = new boolean[m][n]; // 标记是否已访问int result = 0; // 记录岛屿数量// 遍历每个格子for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {// 如果当前格子是陆地(1)且未被访问if (!visited[i][j] && grid[i][j] == 1) {result++; // 发现新岛屿visited[i][j] = true; // 标记为已访问bfs(visited, i, j, grid); // 深度优先搜索,标记整个连通区域}}}System.out.println(result); // 输出岛屿总数scan.close();}/*** 自定义 Pair 类,用于封装坐标 (x, y)*/static class pair {int first;int second;public pair(int first, int second) {this.first = first;this.second = second;}}// 四个方向:右、下、上、左(x, y 增量)public static int[][] dir = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};private static void bfs(boolean[][] visited, int x, int y, int[][] grid) {Queue<pair> queue = new LinkedList<pair>();//定义坐标队列,没有现成的pair类,在下面自定义了queue.add(new pair(x, y));visited[x][y] = true;//遇到入队直接标记为优先,// 否则出队时才标记的话会导致重复访问,比如下方节点会在右下顺序的时候被第二次访问入队while (!queue.isEmpty()) {int curX = queue.peek().first;int curY = queue.poll().second;//当前横纵坐标for (int i = 0; i < 4; i++) {//顺时针遍历新节点next,下面记录坐标int nextX = curX + dir[i][0];int nextY = curY + dir[i][1];if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) {continue;}//去除越界部分if (!visited[nextX][nextY] && grid[nextX][nextY] == 1) {queue.add(new pair(nextX, nextY));visited[nextX][nextY] = true;//逻辑同上}}}}/*** 从坐标 (x, y) 开始进行深度优先搜索,标记所有相连的陆地*/private static void dfs(boolean[][] visited, int x, int y, int[][] grid) {if (visited[x][y] || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水visited[x][y] = true; // 标记为已访问// 向四个方向扩展for (int i = 0; i < 4; i++) {int nextX = x + dir[i][0];int nextY = y + dir[i][1];// 越界检查if (nextX < 0 || nextY < 0 || nextX >= grid.length || nextY >= grid[0].length) {continue;}dfs(visited, nextX, nextY, grid); // 继续递归搜索}}
}
100岛屿的最大面积(深搜-无结束条件版本)
100. 岛屿的最大面积
思路:如果是有结束条件版本,dfs处理当前节点,即在主函数遇到岛屿就计数为0,dfs处理接下来的全部陆地;如果是无结束条件版本,则dfs处理当前节点的相邻节点,即在主函数遇到岛屿就计数为1,dfs处理接下来的相邻陆地
import java.util.*;
import java.math.*;public class Main {// 四个方向:右、下、左、上(顺时针)static final int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};static int result = 0; // 记录最大岛屿面积static int count = 0; // 记录当前岛屿的面积(DFS过程中累加)public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt(); // 网格行数int m = scanner.nextInt(); // 网格列数int[][] map = new int[n][m]; // 存储地图// 输入地图数据for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {map[i][j] = scanner.nextInt();}}boolean[][] visited = new boolean[n][m]; // 标记是否已访问// 遍历每个格子for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {// 发现未访问的陆地,开始新岛屿的DFSif (!visited[i][j] && map[i][j] == 1) {count = 0; // 重置当前岛屿面积计数器dfs(map, visited, i, j); // 深度优先搜索,统计该岛屿面积result = Math.max(count, result); // 更新最大面积}}}System.out.println(result); // 输出最大岛屿面积scanner.close();}/*** DFS:从 (x, y) 开始遍历整个连通的陆地,累加面积*/static void dfs(int[][] map, boolean[][] visited, int x, int y) {count++; // 当前格子是陆地,面积+1visited[x][y] = true; // 标记为已访问,防止重复计算// 向四个方向扩展for (int i = 0; i < 4; i++) {int nextX = x + dir[i][0];int nextY = y + dir[i][1];// 跳过:越界、已访问、海水if (nextX < 0 || nextY < 0 ||nextX >= map.length || nextY >= map[0].length ||visited[nextX][nextY] || map[nextX][nextY] == 0) {continue;}// 递归访问相邻陆地dfs(map, visited, nextX, nextY);}}
}