层序遍历必须用队列而非递归,因递归天然适配深度优先,而层序属广度优先;c++中应使用std::queue,记录每层节点数levelsize后循环出队,避免用stack或动态size导致错误,空树需提前判空,分层统计须在每层for内完成,禁用nullptr占位,子节点入队前须判空。

层序遍历必须用队列,不能用递归
递归天然适合深度优先(前/中/后序),但层序是广度优先,靠函数调用栈无法自然维持“同一层节点集中处理”的顺序。C++标准库的 std::queue 是最直接选择,每次取出队首节点,再把它的左右子节点依次入队——这样能严格保证上层节点一定先于下层节点被访问。
常见错误是误用 std::stack 或手动维护 vector 模拟队列却忘记清空当前层缓存,导致节点混层、重复访问或漏访。
- 别在循环里反复调用
queue.size()作循环条件——因为队列长度在循环中会动态增长,会导致无限循环 - 正确做法:先记录当前层节点数
int levelSize = q.size(),再用 for 循环执行levelSize次出队操作 - 空树要提前判空,否则
q.front()在空队列上调用会崩溃
按层统计需在每轮 for 循环内聚合同层数据
层序遍历本身只解决访问顺序问题;要统计每层节点数、值总和、最大值等,必须在每层的 for 循环内部做聚合。不能等到所有节点遍历完再分组——那样丢失了层级边界信息。
典型场景:返回二维 vector,其中 result[i] 对应第 i 层的所有节点值;或单独维护一个 vector<int></int> 记录每层节点数量。
- 每层开始前定义临时容器,如
vector<int> currLevel</int>或计数器int count = 0 - for 循环中对每个出队节点做处理:存值、累加、更新极值
- for 结束后,把临时结果 push 到最终结果容器中
- 注意:如果只要求节点数量,无需存储具体值,可省去 vector 分配开销
使用 nullptr 占位会导致逻辑混乱,应避免
有人试图用 nullptr 插入队列来标记层结束,比如“遇到 nullptr 就切层”。这看似简洁,但极易出错:一旦某个节点的左/右子指针恰好为 nullptr(正常情况),就会被误判为层边界;而且需要额外判断和跳过,代码可读性差、调试困难。
真正可靠的分层依据只有队列在进入 for 循环前的长度——它精确等于当前层非空节点个数。
- 不要往队列里 push
nullptr,除非你明确需要处理空节点(如序列化) - 子节点入队前必须判空:
if (node->left) q.push(node->left); - 若业务需要保留空位置(如打印满二叉树结构),那是另一层需求,不应和基础层序逻辑耦合
C++17 后可用 structured binding 简化节点访问
如果节点结构体字段命名清晰(如 val, left, right),且你用的是 C++17 或更高版本,可以配合 auto [val, left, right] = *node; 这类解构语法让代码更紧凑。但要注意:这仅适用于结构体/类支持结构化绑定(public 成员或提供相应 get 接口),且不能用于原始指针。
更稳妥通用的做法仍是显式访问成员:node->val、node->left,尤其在团队协作或需兼容旧编译器时。
- 解构语法不改变逻辑,只是写法糖,性能无差异
- 若节点类型是
TreeNode*,必须先解引用再解构:auto& [v, l, r] = *node; - 多数生产环境仍倾向显式写法,避免新语法引入理解门槛
queue 初始化后立即为空,这个分支必须单独处理,否则后续逻辑可能未定义。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











