用std::queue实现bfs层序遍历需先判空根节点,再循环中快照q.size()控制每层处理数量,出队后访问并入队非空子节点,严格避免解引用nullptr。

用 std::queue 实现 BFS 层序遍历是最直接的方式
核心就是维护一个先进先出的队列,每次取出队首节点,访问它,再把它的左右子节点(非空)依次入队。C++ 标准库的 std::queue 天然适配这个需求,不需要手写队列或担心边界错位。
常见错误是只 push 子节点但忘了 pop 当前节点,导致死循环;或者判断子节点是否为空时写成 if (node->left) 却没检查 node 本身是否为空——这在根节点为 nullptr 时直接崩溃。
实操建议:
- 初始化队列前务必判空:
if (!root) return {}; - 每次循环开始前用
int levelSize = q.size();记录本层节点数,避免边遍历边入队导致 size 动态变化 - 不要用
while (!q.empty())然后在里面无条件q.pop(),必须确保 pop 前已成功 front() 取值
按层分组返回结果(二维 vector)的关键是控制每轮处理数量
很多题目要求返回 vector<vector>></vector>,即每层一个子数组。这时候不能简单地把所有节点值顺序压入一维 vector,必须显式区分层级。
典型陷阱是误以为“每次 pop 一个、push 两个”就能自然分层——实际上队列会混入下层节点,不记录当前层长度就会把多层数据搅在一起。
示例逻辑片段:
vector<vector>> result;
if (!root) return result;
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);
}
result.push_back(level);
}
</int></treenode></vector>
使用 nullptr 作为层分隔符的写法容易引发空指针解引用
有人习惯往队列里插入 nullptr 标记层结束,靠它触发换行。这种写法看似直观,但在 C++ 中极易因疏忽导致 node->val 或 node->left 对 nullptr 操作而崩溃。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
除非你明确需要在遍历中做层间隔离(比如打印“---”分隔),否则没必要引入额外的空指针分支判断。相比 levelSize 方案,它增加至少两处 if (node) 判断,且无法规避竞态风险(例如连续两个 nullptr 被插入)。
如果你坚持用分隔符,必须保证:
- 入队
nullptr前,当前层所有真实节点已处理完毕 - 每次取
front()后立即pop(),再统一判断是否为nullptr - 对
nullptr的处理逻辑里禁止任何成员访问操作
迭代器失效与内存安全:别在遍历中 delete 节点
BFS 本身是只读遍历,但实际项目中常有人想边遍历边释放内存。这是危险操作:q.pop() 不会自动销毁节点对象,而手动 delete node 后,若该节点指针仍残留在队列中(比如右子节点刚入队、左子节点还没 pop),后续访问就变成野指针。
正确做法是分离遍历和析构:先完成 BFS 收集所有节点指针(如存入 vector<treenode></treenode>),再统一 delete;或者改用智能指针(shared_ptr<treenode></treenode>)配合自定义删除器。
另外注意:如果树节点由 new 分配但未用 delete,BFS 本身不会导致内存泄漏;但若在循环内写了 delete node 却没置空其父节点的 left/right 指针,上层结构可能在后续访问中重复释放同一地址。
真正要小心的是那个 levelSize 快照时机——它必须在 for 循环外、while 循环体内第一时间获取,晚一步就可能漏掉最后一层。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










