BFS算法五连击:从入门到精通,解密Node结构体的千面应用

1. 腐烂的橘子(LeetCode 994)

题目描述

在 m×n 网格中,每个单元格可以是:

  • 0 表示空单元格

  • 1 表示新鲜橘子

  • 2 表示腐烂橘子

每分钟,腐烂橘子会感染周围4个方向的新鲜橘子。返回所有橘子腐烂所需的最少分钟数,若不可能返回 -1

示例输入
grid = [[2,1,1],[1,1,0],[0,1,1]]
示例输出
4
解释:第4分钟所有橘子腐烂。


解题思路

核心思想:多源BFS层序遍历

  1. 同步扩散:所有腐烂橘子作为起点同时扩散,类似多个火源同时燃烧

  2. 时间计算:每层BFS对应一分钟的感染过程

  3. 终止条件:当没有新鲜橘子时停止

关键技巧

  • 队列初始化时加入所有腐烂橘子

  • 用计数器 fresh 追踪剩余新鲜橘子数量

  • 层序遍历保证时间计算的准确性


️ 代码实现(详细注释)

cpp

#include 
#include 
using namespace std;

class Solution {
public:
    struct Node {
        int x, y;
        Node(int _x, int _y) : x(_x), y(_y) {}
    };

    int orangesRotting(vector>& grid) {
        int m = grid.size(), n = grid[0].size();
        queue q;
        vector dx = {1, 0, -1, 0}, dy = {0, 1, 0, -1};
        int fresh = 0, minutes = 0;

        // 初始化队列并统计新鲜橘子
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 2) {
                    q.push(Node(i, j)); // 腐烂橘子入队
                } else if (grid[i][j] == 1) {
                    fresh++; // 统计需要感染的新鲜橘子
                }
            }
        }

        // BFS层序遍历
        while (!q.empty() && fresh > 0) {
            int levelSize = q.size(); // 当前层的腐烂橘子数量
            for (int i = 0; i < levelSize; ++i) {
                Node cur = q.front(); q.pop();
                // 向四个方向扩散
                for (int k = 0; k < 4; ++k) {
                    int nx = cur.x + dx[k], ny = cur.y + dy[k];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1) {
                        grid[nx][ny] = 2; // 标记为腐烂
                        q.push(Node(nx, ny));
                        fresh--; // 更新新鲜橘子计数
                    }
                }
            }
            minutes++; // 每层代表一分钟
        }

        return fresh == 0 ? minutes : -1;
    }
};

关键细节解析
  1. Node结构体设计:仅需存储坐标 (x, y),时间由层数隐式记录

  2. 层序遍历的意义:队列的每层元素对应同一分钟被感染的橘子

  3. 空间复杂度优化:直接在原数组上修改状态,无需额外存储

  4. 时间复杂度:O(mn),每个单元格最多入队一次

示例流程


2. 迷宫寻出口(LeetCode 1926)

题目描述

在由 +(墙)和 .(路)组成的迷宫中,找到从入口到最近出口的最短路径长度。出口定义为边界上的空格子,入口不算出口。

示例输入
maze = [[".","+",".","+"],[".",".",".","+"],["+","+","+","."]], entrance = [1,2]
示例输出
1
解释:向上一步即可到达边界出口。


解题思路

核心思想:单源BFS最短路径

  1. 路径探索:从入口出发,BFS天然适合找最短路径

  2. 出口判定:移动到边界时立即返回(BFS保证首次到达即最短)

  3. 状态标记:访问过的格子标记为墙防止重复访问

关键技巧

  • 起点入队后立即标记

  • 步数记录在Node结构体中

  • 边界判断优先于常规移动判断


️ 代码实现(详细注释)

cpp

class Solution {
public:
    struct Node {
        int x, y, dist;
        Node(int _x, int _y, int _d) : x(_x), y(_y), dist(_d) {}
    };

    int nearestExit(vector>& maze, vector& entrance) {
        int m = maze.size(), n = maze[0].size();
        vector dx = {1, 0, -1, 0}, dy = {0, 1, 0, -1};
        queue q;

        // 起点入队并标记
        q.push(Node(entrance[0], entrance[1], 0));
        maze[entrance[0]][entrance[1]] = '+';

        while (!q.empty()) {
            Node cur = q.front(); q.pop();
            
            for (int k = 0; k < 4; ++k) {
                int nx = cur.x + dx[k], ny = cur.y + dy[k];
                
                // 边界检查
                if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
                
                if (maze[nx][ny] == '.') {
                    // 到达出口条件
                    if (nx == 0 || nx == m-1 || ny == 0 || ny == n-1) {
                        return cur.dist + 1;
                    }
                    maze[nx][ny] = '+'; // 标记已访问
                    q.push(Node(nx, ny, cur.dist + 1));
                }
            }
        }
        return -1;
    }
};

关键细节解析
  1. Node结构体设计dist 字段记录到达当前坐标的步数

  2. 即时出口判断:在移动前判断新坐标是否在边界

  3. 空间复杂度:O(mn),最坏情况需要存储所有位置

  4. 剪枝优化:提前标记已访问节点,防止重复入队

路径可视化

初始迷宫:
+ + . + 
. . . + 
+ + + . 

BFS扩散过程:
第0步:(1,2)
第1步:到达(0,2)

3. 离陆地最远的海洋(LeetCode 1162)

题目描述

在 0-1 矩阵中找到离所有陆地最远的海洋单元格的曼哈顿距离。

示例输入
grid = [[1,0,1],[0,0,0],[1,0,1]]
示例输出
2
解释:中心海洋到最近陆地的距离是2。


解题思路

核心思想:多源BFS逆向传播

  1. 逆向思维:所有陆地作为起点向海洋扩散

  2. 距离计算:每个海洋单元格记录最近的陆地距离

  3. 最大值追踪:维护全局最大距离

关键技巧

  • 使用二维数组 dist 记录距离

  • 初始时将陆地距离设为0

  • BFS传播时更新海洋单元格距离


️ 代码实现(详细注释)

cpp

class Solution {
public:
    struct Node {
        int x, y, dist;
        Node(int _x, int _y, int _d) : x(_x), y(_y), dist(_d) {}
    };

    int maxDistance(vector>& grid) {
        int m = grid.size(), n = grid[0].size();
        queue q;
        vector> dist(m, vector(n, -1));
        vector dx = {1, 0, -1, 0}, dy = {0, 1, 0, -1};
        int max_dist = -1;

        // 初始化陆地
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 1) {
                    q.push(Node(i, j, 0));
                    dist[i][j] = 0;
                }
            }
        }

        // BFS传播距离
        while (!q.empty()) {
            Node cur = q.front(); q.pop();
            for (int k = 0; k < 4; ++k) {
                int nx = cur.x + dx[k], ny = cur.y + dy[k];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n && dist[nx][ny] == -1) {
                    dist[nx][ny] = cur.dist + 1;
                    max_dist = max(max_dist, dist[nx][ny]);
                    q.push(Node(nx, ny, dist[nx][ny]));
                }
            }
        }

        return max_dist;
    }
};

关键细节解析
  1. Node结构体设计dist 表示当前单元格到最近陆地的距离

  2. 距离传播特性:BFS保证每个海洋单元格首次被访问时即为最短距离

  3. 时间复杂度:O(mn),每个单元格处理一次

  4. 空间优化:复用输入矩阵标记陆地,但使用独立 dist 数组更清晰

算法演示

初始状态:
1 0 1
0 0 0 
1 0 1

距离传播过程:
0 1 0
1 2 1
0 1 0
最大距离2出现在中心

4. 水域高度传播(LeetCode 1765)️

题目描述

给定水域(1)和陆地(0),为陆地安排高度,满足:

  • 水域高度为0

  • 相邻格子高度差≤1
    求可能的最大高度矩阵。

示例输入
isWater = [[0,1],[0,0]]
示例输出
[[1,0],[2,1]]


解题思路

核心思想:多源BFS高度传播

  1. 水域初始化:所有水域高度0作为起点

  2. 层序递增:每层BFS高度+1

  3. 最优性保证:BFS确保每个陆地获得最小可能高度

关键技巧

  • 使用 ans 矩阵同时记录高度和访问状态

  • 队列中存储当前高度值

  • 高度差约束天然满足BFS传播特性


️ 代码实现(详细注释)

cpp

class Solution {
public:
    struct Node {
        int x, y, val;
        Node(int _x, int _y, int _v) : x(_x), y(_y), val(_v) {}
    };

    vector> highestPeak(vector>& isWater) {
        int m = isWater.size(), n = isWater[0].size();
        vector> ans(m, vector(n, -1));
        queue q;
        vector dx = {1,0,-1,0}, dy = {0,1,0,-1};

        // 水域初始化
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (isWater[i][j] == 1) {
                    ans[i][j] = 0;
                    q.push(Node(i, j, 0));
                }
            }
        }

        // BFS传播高度
        while (!q.empty()) {
            Node cur = q.front(); q.pop();
            for (int k = 0; k < 4; ++k) {
                int nx = cur.x + dx[k], ny = cur.y + dy[k];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n && ans[nx][ny] == -1) {
                    ans[nx][ny] = cur.val + 1;
                    q.push(Node(nx, ny, ans[nx][ny]));
                }
            }
        }

        return ans;
    }
};

关键细节解析
  1. Node结构体设计val 记录当前单元格的高度值

  2. 高度传播特性:相邻单元格高度差正好为1,满足题目约束

  3. 正确性证明:BFS队列按高度顺序处理,保证每个单元格首次被访问时得到最小高度

  4. 复杂度分析:时间复杂度O(mn),空间复杂度O(mn)

传播过程示例

初始水域:
0 1
0 0

第1层传播:
1 0
1 1

第2层传播:
1 0 
2 1

5. 最小体力路径(LeetCode 1631)⛰️

题目描述

从矩阵左上角到右下角,找到一条路径使得相邻格子的高度差最大值最小。

示例输入
heights = [[1,2,2],[3,8,2],[5,3,5]]
示例输出
2
解释:路径 [1→3→5→3→5] 的最大高度差为2。


解题思路

核心思想:优先队列优化的Dijkstra算法

  1. 路径评估:维护路径中的最大高度差

  2. 优先队列:总是扩展当前最优路径

  3. 松弛操作:当发现更优路径时更新距离

关键技巧

  • 使用小顶堆按当前最大高度差排序

  • 距离矩阵 dist 记录到达各点的最小消耗

  • 及时剪枝非最优路径


️ 代码实现(详细注释)

cpp

#include 
#include 
using namespace std;

class Solution {
public:
    struct Node {
        int x, y, max_diff;
        Node(int _x, int _y, int _d) : x(_x), y(_y), max_diff(_d) {}
        bool operator<(const Node& o) const {
            return max_diff > o.max_diff; // 小顶堆
        }
    };

    int minimumEffortPath(vector>& heights) {
        int m = heights.size(), n = heights[0].size();
        vector> dist(m, vector(n, INT_MAX));
        priority_queue pq;

        // 起点初始化
        dist[0][0] = 0;
        pq.push(Node(0, 0, 0));

        vector dx = {1,0,-1,0}, dy = {0,1,0,-1};
        while (!pq.empty()) {
            Node cur = pq.top(); pq.pop();
            // 到达终点直接返回
            if (cur.x == m-1 && cur.y == n-1) return cur.max_diff;
            // 剪枝:当前路径已不是最优
            if (cur.max_diff > dist[cur.x][cur.y]) continue;

            for (int k = 0; k < 4; ++k) {
                int nx = cur.x + dx[k], ny = cur.y + dy[k];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                    int new_diff = max(cur.max_diff, abs(heights[nx][ny] - heights[cur.x][cur.y]));
                    if (new_diff < dist[nx][ny]) {
                        dist[nx][ny] = new_diff;
                        pq.push(Node(nx, ny, new_diff));
                    }
                }
            }
        }
        return -1; // 实际不会执行到这里
    }
};

关键细节解析
  1. Node结构体设计max_diff 记录路径中的最大高度差,运算符重载实现小顶堆

  2. Dijkstra特性:保证首次到达终点时即为最优解

  3. 时间复杂度:O(mn log(mn)),优先队列操作主导

  4. 空间优化:可改用曼哈顿距离启发式搜索加速

路径选择示例

复制

高度矩阵:
1 2 2
3 8 2
5 3 5

最优路径选择:
1 → 3 → 5 → 3 → 5
最大高度差:3→5(差2)

总结:Node结构体的设计哲学

通过五道经典题目,我们看到了Node结构体的灵活应用:

题目类型 Node结构体成员 设计要点 核心作用
多源同步扩散 x, y 最小信息原则 坐标定位
单源最短路径 x, y, dist 携带路径长度 步数记录
多源距离传播 x, y, dist 携带传播值 距离计算
数值传播 x, y, val 携带传播数值 高度传递
最优路径选择 x, y, max_diff + 排序 携带评估值并支持优先队列 智能路径选择

核心启示

  1. 按需定制:根据问题需求选择携带的附加信息

  2. 算法适配:通过成员设计适配不同BFS变种(普通队列/优先队列)

  3. 性能平衡:在信息携带量和计算效率之间寻找平衡点

掌握Node结构体的灵活设计,BFS算法就能如同瑞士军刀般应对各种场景挑战。理解数据结构与算法的内在联系,是提升问题解决能力的关键。


欢迎在评论区留下你的思考!如果觉得文章有帮助,请点个赞支持一下~

你可能感兴趣的:(算法-BFS(C++实现),算法,宽度优先)