当前位置: 首页 > news >正文

图论专题(六):“隐式图”的登场!DFS/BFS 攻克「岛屿数量」

哈喽各位,我是前端L。

欢迎来到我们的图论专题第六篇!我们已经学会了如何在“显式”的图(由节点和边列表定义)上进行探险。但如果,地图本身就是一张“网格”呢?

今天,我们要解决的“岛屿数量”问题,是算法面试中最经典、最基础的网格遍历题。它将完美地向我们展示,如何将一个m x n的矩阵,视作一个拥有 m * n 个节点、由“上下左右”关系连接的“隐式图”。而我们的DFS/BFS,就是在这张新地图上“航行”的完美工具。

力扣 200. 岛屿数量

https://leetcode.cn/problems/number-of-islands/

题目分析:

  • 输入:一个 m x n 的二维网格 grid,由 '1' (陆地) 和 '0' (水) 组成。

  • 目标:计算图中“岛屿”的数量。

  • 岛屿定义:由水平或竖直相邻的 '1'(陆地)连接而成的区域,且四周被水('0')环绕。

“Aha!”时刻:将“网格”翻译成“图”

  • 节点 (Vertex):每一个单元格 (r, c) 都是图中的一个节点。

  • 边 (Edge):每个单元格 (r, c) 与它的上 (r-1, c)、下 (r+1, c)、左 (r, c-1)、右 (r, c+1) 邻居之间,都存在一条“隐式”的边。

  • 我们要找什么?:我们只关心由 '1'(陆地)构成的“连通区域”。

  • 问题被完美转化: 计算这个“隐式图”中,由 '1' 构成的“连通分量 (Connected Components)”的个数。

解决方案:“淹没”岛屿 (Flood Fill)

如何计算“连通分量”的个数? 我们需要一个“侦察兵”(主循环)和一个“作战部队”(DFS/BFS)。

算法流程:

  1. 初始化岛屿计数 islandCount = 0

  2. “侦察兵”出动:用两层 for 循环,遍历矩阵中的每一个单元格 (r, c)

  3. 发现新目标:在遍历时,if (grid[r][c] == '1')

    • “Aha!” 我们发现了一块“陆地”!

    • 由于我们的“作战部队”会把访问过的陆地都“淹没”(标记掉),所以,任何时候我们遇到的 '1',都必定是一个全新的、未被发现的岛屿的“登陆点”。

    • islandCount++

  4. “作战部队”出动

    • (r, c) 这个“登陆点”开始,启动一次 DFS 或 BFS

    • 这个 DFS/BFS 的任务,就是“淹没 (Flood Fill)”:将所有与 (r, c) 连通的、同属于这个岛屿的 '1' ,全部标记为已访问(比如,直接改成 '0''2'),防止它们被“侦察兵”重复发现。

  5. for 循环结束后,islandCount 就是最终答案。

代码实现 (O(V+E) -> O(m*n))

解法一:DFS (递归“淹没”)

C++

#include <vector>using namespace std;class Solution {
private:// “作战部队”:DFS 函数// 任务:从 (r, c) 出发,淹没所有相连的 '1'void dfs_sink(vector<vector<char>>& grid, int r, int c) {int m = grid.size();int n = grid[0].size();// 1. Base Case (越界或遇到水)if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == '0') {return;}// 2. “淹没” (标记为已访问)grid[r][c] = '0';// 3. 递归探索邻居dfs_sink(grid, r + 1, c); // 下dfs_sink(grid, r - 1, c); // 上dfs_sink(grid, r, c + 1); // 右dfs_sink(grid, r, c - 1); // 左}public:int numIslands(vector<vector<char>>& grid) {if (grid.empty() || grid[0].empty()) {return 0;}int m = grid.size();int n = grid[0].size();int islandCount = 0;// “侦察兵”:遍历所有单元格for (int r = 0; r < m; ++r) {for (int c = 0; c < n; ++c) {if (grid[r][c] == '1') {// 发现了新岛屿!islandCount++;// “作战部队”出动,淹没它dfs_sink(grid, r, c);}}}return islandCount;}
};

解法二:BFS (队列“淹没”)

C++

#include <vector>
#include <queue>using namespace std;class Solution_BFS {
public:int numIslands(vector<vector<char>>& grid) {if (grid.empty() || grid[0].empty()) {return 0;}int m = grid.size();int n = grid[0].size();int islandCount = 0;// 邻居的方向数组int dr[] = {0, 0, 1, -1};int dc[] = {1, -1, 0, 0};for (int r = 0; r < m; ++r) {for (int c = 0; c < n; ++c) {if (grid[r][c] == '1') {islandCount++;grid[r][c] = '0'; // 标记为已访问queue<pair<int, int>> q;q.push({r, c});while (!q.empty()) {pair<int, int> curr = q.front();q.pop();// 探索4个邻居for (int i = 0; i < 4; ++i) {int nr = curr.first + dr[i];int nc = curr.second + dc[i];// 检查邻居是否合法且是 '1'if (nr >= 0 && nr < m && nc >= 0 && nc < n && grid[nr][nc] == '1') {grid[nr][nc] = '0'; // 淹没q.push({nr, nc});}}}}}}return islandCount;}
};

深度复杂度分析

  • V (Vertices):顶点数,即 m * n

  • E (Edges):边数,每个顶点最多4条边,所以 E 最多是 4 * m * n 的级别。

  • 时间复杂度 O(m * n)

    • 我们的“侦察兵” for 循环,会访问 m * n 个单元格。

    • “作战部队” (DFS/BFS) 会在 grid[r][c] == '1' 时启动。由于启动后它会“淹没”所有它能到达的 1确保了每个 '1' 单元格,只会被 DFS/BFS 核心逻辑访问一次

    • 总的来看,每个单元格 (r, c)(无论是0还是1)都被主循环和遍历逻辑,常数次地访问。

    • 总时间复杂度 O(V + E) -> O(mn + 4m*n) -> O(m * n)

  • 空间复杂度

    • DFSO(m * n)。在最坏情况下(一个“蛇形”岛屿占满了整个网格),递归栈的深度可能是 m * n

    • BFSO(min(m, n))。在最坏情况下(比如一个“棋盘格”),队列的大小最多是 min(m, n) 级别。(修正:一个“圆形”岛屿,队列大小可能达到 O(mn))*。(再修正:BFS的最坏空间是O(V),即 O(m*n),例如一个从 (0,0) 开始的巨大岛屿)。

    • (注:如果我们不使用“原地修改” grid[r][c]='0',而是用一个 visited[m][n] 数组,那么空间复杂度会额外增加 O(mn))*

总结

今天,我们打响了“图论”专题的“隐式图”第一枪!

  • “二维网格” = “隐式图”

  • “岛屿数量” = “连通分量个数”

  • DFS/BFS + visited (或原地修改) = “淹没算法 (Flood Fill)”

这个“网格即图”的思维模型,是图论应用中最重要、最常见的模式。

在下一篇中,我们将继续使用这个模型,但我们的任务不再是“计数”,而是要计算“岛屿的最大面积”!

下期见!

http://www.dtcms.com/a/610080.html

相关文章:

  • 当Rokid遇见BOLON,科技与时尚的这次握手重新定义“眼镜”
  • 图论专题(五):图遍历的“终极考验”——深度「克隆图」
  • 商业网站策划书模板范文asp 网站发布器
  • STM32WB55官方OTA例程
  • [Column] How Databricks Implemented Intelligent K8s Load Balancing
  • 网站建设好多钱菏泽郓城网站建设
  • 做互联网营销一般上什么网站cms系统都有哪些
  • 【算法】回溯算法精讲:从深度优先搜索到剪枝优化​
  • C语言知识体系梳理-第一篇
  • 克隆网站怎么做长沙网站建设公司名单
  • 【ASP.NET进阶】Controller 层基础:从 MVC 5 到 Core,继承的奥秘与避坑指南
  • PyTorch深度学习进阶(四)(数据增广)
  • 股指期货豁免开通条件是什么?
  • 上传模型/数据集到huggingface的三种方法
  • 33_FastMCP 2.x 中文文档之FastMCP客户端核心业务:提示模板详解
  • wordpress插件访客亚马逊seo推广
  • Juc篇-线程安全问题引入(从i++问题的底层出发)
  • Arbess V2.1.7版本发布,新增任务AliYun OSS上传、下载功能,新增流水线评审功能
  • 算法基础篇:(八)贪心算法之简单贪心:从直觉到逻辑的实战指南
  • 昊源建设监理有限公司网站外贸网站代码
  • 大专生就业:学历限制的现实考量与能力突围路径
  • Node.js 与 Docker 深度整合:轻松部署与管理 Node.js 应用
  • 中国企业500强榜单2021廊坊seo排名优化
  • 基于高光谱成像和偏最小二乘法(PLS)的苹果糖度检测MATLAB实现
  • 随访系统如何支持临床研究和数据分析?
  • idea 刷新maven,提示java.lang.RuntimeException: java.lang.OutOfMemoryError
  • 邢台本地网站vue做的pc线上网站
  • Arang Briket木炭块检测与识别:基于Mask R-CNN的精确识别方案详解
  • 怎么在百度建设一个网站工业设计大学排名前50
  • 【C++:封装红黑树】C++红黑树封装实战:从零实现MyMap与MySet