文章目录
-
- [BFS 的奥义:涟漪式扩散,掌控全局](#BFS 的奥义:涟漪式扩散,掌控全局)
- [一、 N 叉树的层序遍历:打破维度的墙 (Medium)](#一、 N 叉树的层序遍历:打破维度的墙 (Medium))
-
- [1.1 题目描述](#1.1 题目描述)
- [1.2 算法思路:队列控制层级](#1.2 算法思路:队列控制层级)
- [1.3 C++ 代码实战](#1.3 C++ 代码实战)
- [二、 二叉树的锯齿形层序遍历:灵活的翻转 (Medium)](#二、 二叉树的锯齿形层序遍历:灵活的翻转 (Medium))
-
- [2.1 题目描述](#2.1 题目描述)
- [2.2 算法思路:偶数层大反转](#2.2 算法思路:偶数层大反转)
- [2.3 C++ 代码实战](#2.3 C++ 代码实战)
- [三、 二叉树的最大宽度:坐标映射的威力 (Medium)](#三、 二叉树的最大宽度:坐标映射的威力 (Medium))
-
- [3.1 题目描述](#3.1 题目描述)
- [3.2 深度拆解:索引编号法](#3.2 深度拆解:索引编号法)
- [3.3 C++ 代码实战](#3.3 C++ 代码实战)
- [四、 在每个树行中找最大值 (Medium)](#四、 在每个树行中找最大值 (Medium))
-
- [4.1 题目描述](#4.1 题目描述)
- [4.2 算法思路](#4.2 算法思路)
- [4.3 C++ 代码实战](#4.3 C++ 代码实战)
- [五、 总结:BFS 的涟漪扩散本质](#五、 总结:BFS 的涟漪扩散本质)
BFS 的奥义:涟漪式扩散,掌控全局
一、 N 叉树的层序遍历:打破维度的墙 (Medium)
1.1 题目描述
题目链接 :429. N 叉树的层序遍历
描述 :
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
1.2 算法思路:队列控制层级
BFS 的标准模板:
- 入队起点:先把根节点扔进队列。
- 按层处理 :记录当前队列的大小
sz,这代表了这一层有多少个节点。 - 循环扩展 :处理完这
sz个节点,并把它们的所有孩子依次入队。
核心技巧 :通过 q.size() 确定当前层的数量,可以完美区分出结果数组里的每一行。
1.3 C++ 代码实战
cpp
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> ret;
if (!root) return ret;
queue<Node*> q;
q.push(root);
while (!q.empty()) {
int sz = q.size(); // 锁定当前层的节点数
vector<int> currentLevel;
for (int i = 0; i < sz; i++) {
Node* t = q.front();
q.pop();
currentLevel.push_back(t->val);
// N 叉树:遍历所有孩子并入队
for (Node* child : t->children) {
if (child) q.push(child);
}
}
ret.push_back(currentLevel);
}
return ret;
}
};
二、 二叉树的锯齿形层序遍历:灵活的翻转 (Medium)
2.1 题目描述
题目链接 :103. 二叉树的锯齿形层序遍历
描述 :
先从左往右,再从右往左,层与层之间交替进行。
2.2 算法思路:偶数层大反转
这题没必要在入队、出队顺序上纠结得半死。
- 第一步 :按照正常的层序遍历,把每一层的数据存进
vector<int> tmp。 - 第二步 :维护一个层数变量
level(从 1 开始)。如果是偶数层,直接把tmp反转(reverse) 即可。
ASCII 逻辑图:
bash
L1: [3] -> 正常
L2: [9, 20] -> 偶数层, 反转 -> [20, 9]
L3: [15, 7] -> 奇数层, 正常
2.3 C++ 代码实战
cpp
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>> ret;
if (!root) return ret;
queue<TreeNode*> q;
q.push(root);
int level = 1;
while (!q.empty()) {
int sz = q.size();
vector<int> tmp;
for (int i = 0; i < sz; i++) {
TreeNode* t = q.front();
q.pop();
tmp.push_back(t->val);
if (t->left) q.push(t->left);
if (t->right) q.push(t->right);
}
// 核心逻辑:锯齿形变换
if (level % 2 == 0) reverse(tmp.begin(), tmp.end());
ret.push_back(tmp);
level++;
}
return ret;
}
};
三、 二叉树的最大宽度:坐标映射的威力 (Medium)
3.1 题目描述
题目链接 :662. 二叉树最大宽度
描述 :
每一层的宽度定义为该层最左和最右非空节点之间的长度(包括中间的 null 节点)。
3.2 深度拆解:索引编号法
这道题不能直接数节点,因为 null 也占位置,如果直接计算时间和空间复杂度都会爆炸。
-
破局点:利用完全二叉树的性质给节点编号。
- 根节点编号为
i。 - 左孩子编号为
2 * i。 - 右孩子编号为
2 * i + 1。
- 根节点编号为
-
计算宽度 :每一层的宽度 =
最右节点编号 - 最左节点编号 + 1。
细节坑点 :二叉树极深时,编号会溢出 ,远超long long。
解药 :使用 unsigned int。在 C++ 中,无符号整型溢出后相减,其结果依然是正确的"距离"(环形模运算特性)。(前提是这两个溢出的数值之差不能超过unsigned int表示最大值,也就是不能超过这个环的一圈)
3.3 C++ 代码实战
cpp
class Solution {
public:
int widthOfBinaryTree(TreeNode* root) {
if (!root) return 0;
// 队列存节点及其对应的编号 (unsigned int 防止溢出报错)
queue<pair<TreeNode*, unsigned int>> q;
q.push({root, 1});
unsigned int maxWidth = 0;
while (!q.empty()) {
int sz = q.size();
unsigned int leftIdx = q.front().second; // 拿到本层最左编号
unsigned int rightIdx = 0;
for (int i = 0; i < sz; i++) {
auto [node, idx] = q.front();
q.pop();
rightIdx = idx; // 循环结束时,rightIdx 就是最右编号
if (node->left) q.push({node->left, idx * 2});
if (node->right) q.push({node->right, idx * 2 + 1});
}
maxWidth = max(maxWidth, rightIdx - leftIdx + 1);
}
return (int)maxWidth;
}
};
四、 在每个树行中找最大值 (Medium)
4.1 题目描述
题目链接 :515. 在每个树行中找最大值
4.2 算法思路
这是 BFS 最简单的变体。在处理每一层的 sz 个节点时,顺便维护一个 maxVal 变量,遍历完一层后存入结果数组。
4.3 C++ 代码实战
cpp
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
vector<int> ret;
if (!root) return ret;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int sz = q.size();
int maxVal = INT_MIN; // 每一层初始化为最小值
for (int i = 0; i < sz; i++) {
TreeNode* t = q.front();
q.pop();
maxVal = max(maxVal, t->val);
if (t->left) q.push(t->left);
if (t->right) q.push(t->right);
}
ret.push_back(maxVal);
}
return ret;
}
};
五、 总结:BFS 的涟漪扩散本质
💬 复盘:
-
层级控制的核心 :
BFS 的灵魂在于"按层推进 "。通过
q.size()锁定当前层节点数,我们就能精准地将问题拆分成一层一层的子问题。 -
扩散模型 :
BFS 本质是一种从起点向外一圈一圈扩散的过程,就像水波(涟漪)一样:
- 当前层 = 当前波纹
- 入队孩子 = 下一层波纹的扩展
-
状态附加技巧 :
当题目需要额外信息时(如编号、方向、距离等),可以在队列中存:
cpppair<节点, 状态>👉 例如:
- 最大宽度:存
(node, index) - 最短路径:存
(node, distance)
- 最大宽度:存
-
结构不变,逻辑可变 :
BFS 的框架几乎固定:
cppwhile (!q.empty()) { int sz = q.size(); // 处理当前层 }👉 真正变化的是:
- 每层"统计什么"(最大值 / 顺序 / 宽度)
- 是否对结果做"变换"(如 reverse)
-
降维思想 :
BFS 的强大之处在于:
👉 把"复杂结构问题"转化为"逐层处理问题"
每一层都是一个"线性问题",难度被大幅降低。
🚀 一句话总结:
text
BFS = 队列 + 分层 + 扩散
🔥 核心直觉:
👉 只要题目出现:
- "逐层"
- "最短路径"
- "扩散 / 传播"
第一反应:BFS!