垂序遍历需按列号分组并显式记录(row, col, value),同一列内按行号升序、行号相同时按值升序排序;必须用map存储,且每列内部须二次sort。

垂序遍历的核心是列号(column)而非深度优先顺序
二叉树垂序遍历不是按层或按深度,而是把根节点当作列 0,左子节点列号减 1,右子节点列号加 1。同一列的节点要按行号(row)升序排列;行号相同时,按节点值升序。关键在于:**不能只靠 DFS 或 BFS 顺序遍历就完事,必须显式记录每个节点的 (row, col, value) 三元组**。
常见错误是只用 map
- DFS 中需传入当前
row和col,每递归一层row++,左走col-1,右走col+1 - BFS 更直观:用
queue<tuple int>></tuple>存节点+行+列,出队时直接记录 - 列号范围可能为负(如一直往左),所以必须用
map<int vector>>></int>或unordered_map配合最后取键排序
用 map<int vector>>></int> 自动按列排序
map 的 key 是列号,天然升序;value 存 tuple<row value node_ptr></row>(实际只需 row 和 value,node_ptr 不必存)。插入后对每个 vector 按 row 升序,row 相同时按 value 升序。
别用 unordered_map 然后手动 sort keys——多一次遍历且易漏处理空列;也别用 vector
- 推荐 tuple:用
make_tuple(row, val, dummy),排序时sort(v.begin(), v.end())就按字典序生效(row 优先,再 val) - 如果不用 tuple,可用
vector<array>></array>存{row, val},排序逻辑一样 - 注意:C++17 起
tuple支持结构化绑定,但排序时仍需get(t)等,不如 array 直观
DFS 实现中容易忽略的 row 传递与初始化
DFS 入口调用必须从 row = 0、col = 0 开始;递归进左子树时传 row + 1, col - 1,进右子树时传 row + 1, col + 1。一个典型错误是把 row 写成全局变量或静态变量——多组测试会污染状态。
另一个坑是:有些实现用 depth 替代 row,但若树不完整(有空节点跳过),depth 和真实行号仍一致;只要每次递归都进一层,row 就是 depth。
- DFS 函数签名建议为:
void dfs(TreeNode* node, int row, int col, map<int vector>>>& cols)</int> - 空节点直接 return,不 push;只有非空节点才
cols[col].push_back({row, node->val}) - 别在递归前 push,否则空节点也会占位(比如某列只有空左子节点,不该产生该列记录)
最终输出前必须对每列内部二次排序
即使你用 map 搞定了列间顺序,每列内的节点仍可能乱序:DFS 下左子树先访问,但它的 row 可能比同列右子树的深;BFS 虽按 row 层级推进,但同层节点左右顺序不保证同列内 row 一致(比如 root 的左子节点和右子节点的孙子节点可能同列)。
所以,对每个 cols[col],必须显式 sort()。默认 array<int></int> 或 tuple 的 operator
- 错误写法:
for (auto& p : cols) result.push_back(p.second)—— 缺少 sort - 正确写法:
for (auto& p : cols) { sort(p.second.begin(), p.second.end()); result.push_back(...); } - 如果题目要求“同一列中 row 相同则按原始输入顺序”,那得额外存 index,但标准垂序遍历题(如 LeetCode 987)明确要求按值升序
列号可能为负,map 天然支持;但如果你用 vector 硬做偏移映射,min_col 和 max_col 得两次遍历才能确定——不必要地增加复杂度和出错点。老老实实用 map,接受它 O(log n) 插入,整体仍是 O(n log n)。真正卡性能的从来不是 map,而是忘了对每列 sort。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











