代码随想录Day51:图论(岛屿数量 深搜&广搜、岛屿的最大面积)

发布于:2025-08-17 ⋅ 阅读:(12) ⋅ 点赞:(0)

一、实战

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++) {
                // 发现未访问的陆地,开始新岛屿的DFS
                if (!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++; // 当前格子是陆地,面积+1
        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 >= map.length || nextY >= map[0].length ||
                visited[nextX][nextY] || map[nextX][nextY] == 0) {
                continue;
            }

            // 递归访问相邻陆地
            dfs(map, visited, nextX, nextY);
        }
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到