Day51–图论–99. 岛屿数量(卡码网),100. 岛屿的最大面积(卡码网)
本篇重点,在于理解深搜和广搜遍历顺序的不同,以及深搜广搜对应的坑点。
给出了打印遍历顺序的代码,可以打印出来遍历点直观感受一下。
可以看到,DFS就是不撞南墙不回头;BFS就是像水波纹一样一层一层荡漾开来。
99. 岛屿数量(卡码网)
方法:深搜
思路:
本题思路,是遇到一个没有遍历过的陆地,计数器就加一,然后把该节点所能遍历到的陆地都标记上。
在遇到标记过的陆地节点或海洋节点的时候直接跳过。 这样计数器就是最终岛屿的数量。
import java.util.*;
public class Main {
// 四个方向:右、下、左,上
private static final int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private static void dfs(int[][] grid, boolean[][] visited, int x, int y) {
// 四个方向都遍历一遍
for (int i = 0; i < 4; i++) {
int nextX = x + dir[i][0];
int nextY = y + dir[i][1];
// 检查是否越界
if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) {
continue;
}
// 如果未访问且是陆地(1),则继续DFS
if (!visited[nextX][nextY] && grid[nextX][nextY] == 1) {
visited[nextX][nextY] = true;
dfs(grid, visited, nextX, nextY);
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int[][] grid = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
grid[i][j] = in.nextInt();
}
}
// 访问标记数组
boolean[][] visited = new boolean[n][m];
// 计数器
int count = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 发现未访问的陆地,岛屿数量加1,并开始DFS标记所有相连的陆地
if (!visited[i][j] && grid[i][j] == 1) {
visited[i][j] = true;
count++;
dfs(grid, visited, i, j);
}
}
}
System.out.println(count);
in.close();
}
}
遍历顺序:
DFS访问顺序:
1 2 0 0 0
4 3 0 0 0
0 0 5 0 0
0 0 0 6 7
方法:广搜
思路:
只要加入队列就代表走过,就需要标记,而不是从队列拿出来的时候再去标记走过。
import java.util.*;
public class Main {
// 四个方向:右、下、左,上
private static final int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
// 广搜
private static void bfs(int[][] grid, boolean[][] visited, int x, int y) {
Deque<int[]> que = new ArrayDeque<>();
// 存储节点坐标
que.offer(new int[] {x, y});
// 加入队列后立即标记为已访问
visited[x][y] = true;
while (!que.isEmpty()) {
int[] cur = que.poll();
int curX = cur[0];
int curY = cur[1];
// 四个方向都遍历一遍
for (int i = 0; i < 4; i++) {
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) {
que.offer(new int[] {nextX, nextY});
visited[nextX][nextY] = true;
}
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int[][] grid = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
grid[i][j] = in.nextInt();
}
}
// 访问标记数组
boolean[][] visited = new boolean[n][m];
// 计数器
int count = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 发现未访问的陆地,岛屿数量加1,并开始BFS标记所有相连的陆地
if (!visited[i][j] && grid[i][j] == 1) {
count++;
bfs(grid, visited, i, j);
}
}
}
System.out.println(count);
in.close();
}
}
遍历顺序:
DFS访问顺序:
1 2 0 0 0
4 3 0 0 0
0 0 5 0 0
0 0 0 6 7
BFS访问顺序:
1 2 0 0 0
3 4 0 0 0
0 0 5 0 0
0 0 0 6 7
遍历顺序打印
思路:
可以复制代码,修改代码中的矩阵,打印出来,直观看看每一步都是怎么走的。
import java.util.LinkedList;
import java.util.Queue;
public class TraversalOrder {
// 四个方向:右、下、左,上
private static final int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private static int count;
// DFS遍历并记录访问顺序
private static void dfs(int[][] grid, int[][] order, int x, int y) {
for (int i = 0; i < 4; i++) {
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx >= 0 && nextx < grid.length && nexty >= 0 && nexty < grid[0].length) {
if (grid[nextx][nexty] == 1 && order[nextx][nexty] == 0) {
count++;
order[nextx][nexty] = count;
dfs(grid, order, nextx, nexty);
}
}
}
}
// BFS遍历并记录访问顺序
private static void bfs(int[][] grid, int[][] order, int x, int y) {
Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[]{x, y});
count++;
order[x][y] = count;
while (!queue.isEmpty()) {
int[] cur = queue.poll();
int curx = cur[0];
int cury = cur[1];
for (int i = 0; i < 4; i++) {
int nextx = curx + dir[i][0];
int nexty = cury + dir[i][1];
if (nextx >= 0 && nextx < grid.length && nexty >= 0 && nexty < grid[0].length) {
if (grid[nextx][nexty] == 1 && order[nextx][nexty] == 0) {
count++;
order[nextx][nexty] = count;
queue.offer(new int[]{nextx, nexty});
}
}
}
}
}
// 打印访问顺序矩阵
private static void printOrder(int[][] order) {
for (int i = 0; i < order.length; i++) {
for (int j = 0; j < order[0].length; j++) {
System.out.printf("%2d ", order[i][j]);
}
System.out.println();
}
}
public static void main(String[] args) {
// 初始化给定的矩阵
int[][] grid = {
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1}
};
int n = grid.length;
int m = grid[0].length;
// 演示DFS访问顺序
System.out.println("DFS访问顺序:");
int[][] dfsOrder = new int[n][m];
count = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 1 && dfsOrder[i][j] == 0) {
count++;
dfsOrder[i][j] = count;
dfs(grid, dfsOrder, i, j);
}
}
}
printOrder(dfsOrder);
// 演示BFS访问顺序
System.out.println("\nBFS访问顺序:");
int[][] bfsOrder = new int[n][m];
count = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 1 && bfsOrder[i][j] == 0) {
bfs(grid, bfsOrder, i, j);
}
}
}
printOrder(bfsOrder);
}
}
目前代码打印效果
DFS访问顺序: 1 2 3 4 5 22 23 24 25 6 21 20 19 18 7 14 15 16 17 8 13 12 11 10 9 BFS访问顺序: 1 2 4 7 11 3 5 8 12 16 6 9 13 17 20 10 14 18 21 23 15 19 22 24 25
可以看到,DFS就是不撞南墙不回头;BFS就是像水波纹一样一层一层荡漾开来。
100. 岛屿的最大面积(卡码网)
方法:深搜
思路:
在dfs递归的时候统计,每一层递归向上返回本次遍历到的岛屿数。
主函数里的循环,每次都是遇到一个新的岛屿,所以在主函数里,dfs回来之后,刷新maxLand。
import java.util.*;
public class Main {
// 四个方向:右、下、左,上
private static final int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private static int dfs(int[][] grid, boolean[][] visited, int x, int y) {
// 坐标为x,y的本节点
int sum = 1;
// 四个方向都遍历一遍
for (int i = 0; i < 4; i++) {
int nextX = x + dir[i][0];
int nextY = y + dir[i][1];
// 检查是否越界
if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) {
continue;
}
// 如果未访问且是陆地(1),则继续DFS
if (!visited[nextX][nextY] && grid[nextX][nextY] == 1) {
visited[nextX][nextY] = true;
// 加上从这个节点出发,深搜到的所有节点
sum += dfs(grid, visited, nextX, nextY);
}
}
// 返回给上一层
return sum;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int[][] grid = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
grid[i][j] = in.nextInt();
}
}
// 访问标记数组
boolean[][] visited = new boolean[n][m];
int maxLand = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 发现未访问的陆地,岛屿数量加1,并开始DFS标记所有相连的陆地
if (!visited[i][j] && grid[i][j] == 1) {
visited[i][j] = true;
// 每次从这里进去的,是一整个岛屿
int land = dfs(grid, visited, i, j);
// 刷新最大岛屿面积
maxLand = Math.max(maxLand, land);
}
}
}
System.out.println(maxLand);
in.close();
}
}
方法:深搜
思路:
全局变量maxLand和count。
主函数里的循环,每次都是遇到一个新的岛屿,所以在主函数里,dfs之前,重置count=0,dfs回来之后,刷新maxLand。
import java.util.*;
public class Main {
// 四个方向:右、下、左,上
private static final int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private static int maxLand = 0;
private static int count = 0;
private static void dfs(int[][] grid, boolean[][] visited, int x, int y) {
count++;
// 四个方向都遍历一遍
for (int i = 0; i < 4; i++) {
int nextX = x + dir[i][0];
int nextY = y + dir[i][1];
// 检查是否越界
if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) {
continue;
}
// 如果未访问且是陆地(1),则继续DFS
if (!visited[nextX][nextY] && grid[nextX][nextY] == 1) {
visited[nextX][nextY] = true;
dfs(grid, visited, nextX, nextY);
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int[][] grid = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
grid[i][j] = in.nextInt();
}
}
// 访问标记数组
boolean[][] visited = new boolean[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 发现未访问的陆地,岛屿数量加1,并开始DFS标记所有相连的陆地
if (!visited[i][j] && grid[i][j] == 1) {
visited[i][j] = true;
count = 0;
dfs(grid, visited, i, j);
maxLand = Math.max(res, count);
}
}
}
System.out.println(maxLand);
in.close();
}
}
方法:广搜
思路:
同上,接着用全局变量的方法,使用maxLand和count计数。
当队列que出队的时候,count++;
当回到主函数的时候,刷新maxLand。
import java.util.*;
public class Main {
// 四个方向:右、下、左,上
private static final int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private static int maxLand = 0;
private static int count = 0;
// 广搜
private static void bfs(int[][] grid, boolean[][] visited, int x, int y) {
Deque<int[]> que = new ArrayDeque<>();
// 存储节点坐标
que.offer(new int[] {x, y});
// 加入队列后立即标记为已访问
visited[x][y] = true;
while (!que.isEmpty()) {
// 每次弹出队列的时候,节点+1,岛屿面积++
count++;
int[] cur = que.poll();
int curX = cur[0];
int curY = cur[1];
// 四个方向都遍历一遍
for (int i = 0; i < 4; i++) {
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) {
que.offer(new int[] {nextX, nextY});
visited[nextX][nextY] = true;
}
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int[][] grid = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
grid[i][j] = in.nextInt();
}
}
// 访问标记数组
boolean[][] visited = new boolean[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 发现未访问的陆地,岛屿数量加1,并开始BFS标记所有相连的陆地
if (!visited[i][j] && grid[i][j] == 1) {
count = 0;
bfs(grid, visited, i, j);
maxLand = Math.max(maxLand, count);
}
}
}
System.out.println(maxLand);
in.close();
}
}