应使用 std::queue 而非 std::vector 模拟队列,因其 front()/pop()/push() 语义清晰、o(1) 时间复杂度,而 vector.erase(begin()) 为 o(n);treenode* 入队前须判空,避免段错误与逻辑混乱;层序遍历需两层循环并预先记录 size,确保层级准确。

为什么用 std::queue 而不是 std::vector 模拟队列
因为 BFS 的核心是「先进先出」,std::queue 的 front()/pop()/push() 接口语义清晰、常数时间复杂度,且底层默认基于 std::deque,兼顾缓存局部性与动态扩容。若强行用 std::vector + erase(begin()),每次删除首元素会触发 O(n) 移动,严重拖慢层序遍历性能。
常见错误现象:vector.erase(vector.begin()) 在每层循环中反复调用,节点多时卡顿明显,甚至超时。
- 不要手动维护索引模拟队列(如
int l = 0, r = 0),易越界且可读性差 - 避免在循环中对
queue做size()以外的修改(如嵌套 push/pop) - 如果需访问下一层全部节点,必须在进入内层循环前记录当前队列大小 —— 这是分层的关键
TreeNode* 指针入队时为何必须判空
二叉树中子节点为 nullptr 是常态,不检查就直接 q.push(node->left) 不会崩溃,但后续 q.front()->val 会触发段错误。更隐蔽的问题是:空指针入队后,你无法区分它是“有效空节点”还是“非法解引用残留”,导致逻辑混乱。
典型错误场景:层序输出每层最大值时,因未跳过 nullptr,导致 max_element 报错或结果错乱。
- 每次从队列取节点后,立即检查
if (!node) continue;(虽冗余但安全) - 更推荐写法:只在生成子节点时判空,即
if (node->left) q.push(node->left); - LeetCode 类题目中,输入树不含空根,但子树可能全空,务必按子节点粒度检查
如何稳定提取「每层节点值」并保留层级结构
关键不在队列本身,而在循环结构 —— 必须用两层循环:while (!q.empty()) 控制层数,内层 for (int i = 0; i 固定处理当前层所有节点。levelSize 来自进入内层前的 <code>q.size(),不能在内层循环中动态调用。
否则会出现:新入队的下层节点被误算进当前层,导致结果扁平化(所有值挤在第一层 vector 中)。
vector<vector>> res;
queue<treenode> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size(); // ← 必须放这里!
vector<int> level;
for (int i = 0; i val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
res.push_back(level);
}
</int></treenode></vector>
非递归 BFS 层序遍历的边界情况处理
输入为 nullptr 根节点时,q.push(root) 会压入空指针,后续 q.front() 解引用崩溃。这不是队列问题,而是入口校验缺失。
容易被忽略的点:有些实现把 if (!root) return {}; 放在函数末尾或包在 if 里,但只要队列操作在判空前执行,就已出错。
- 必须在任何队列操作前判断
if (!root) return {}; - 若需返回空层(如 [[]]),则单独处理:先 push(nullptr),再在内层检查 node 是否为空并跳过
- 多叉树扩展时,子节点容器遍历中同样要逐个判空,不能只判父节点
实际写的时候,最常掉坑的是把 q.size() 写进 for 循环条件里,看着简洁,实则每次迭代都重算且受 push 影响 —— 这个细节一错,整个层级就塌了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











