二叉树边界遍历是什么?左边界、叶子和右边界为什么要分开处理?
简化版
边界遍历通常按“根节点 → 左边界 → 所有叶子 → 逆序右边界”输出。三部分分开处理,是为了避免重复输出叶子和根节点,并保证边界顺序符合逆时针轮廓。
详细版
边界遍历关注树的外轮廓。常见逆时针输出规则:
- 先输出根节点。
- 输出左边界,不包括叶子。
- 输出所有叶子,从左到右。
- 输出右边界,不包括叶子,最后逆序加入。
左边界优先走左孩子,没有左孩子才走右孩子;右边界优先走右孩子,没有右孩子才走左孩子。叶子单独 DFS 收集,避免在左右边界里重复。
完整版教学
一、边界遍历想输出什么
边界遍历不是普通遍历顺序,而是树的外轮廓。如果从根开始逆时针绕树一圈,会经过左边界、底部叶子、右边界。
1
/ \
2 3
/ \ \
4 5 6
边界可能是 1,2,4,5,6,3。注意 4、5、6 是叶子,3 是右边界逆序回来的部分。
二、为什么左边界不包含叶子
如果左边界包含叶子,后面收集叶子时会重复。例如左边界一路走到 4,叶子收集也会收集 4。
所以常见做法是:
左边界:不含根,不含叶子
叶子:所有叶子
右边界:不含根,不含叶子,逆序
这样三段互不重叠,拼起来刚好覆盖外轮廓。
三、左边界怎么走才正确
左边界优先沿左孩子走。如果没有左孩子,但有右孩子,右孩子也可能成为左侧外轮廓的一部分。
1
/
2
\
3
节点 3 虽然是右孩子,但在这棵子树里它仍处在左边界路径上。因此规则是“有左走左,无左走右”,直到叶子前停止。
四、叶子为什么要从左到右收集
叶子是底部边界,应该按从左到右顺序输出。可以用 DFS:
function addLeaves(node) {
if (!node) return;
if (!node.left && !node.right) add(node);
addLeaves(node.left);
addLeaves(node.right);
}
先递归左子树再右子树,天然得到从左到右的叶子顺序。这里会包含只有一个节点的树的根,所以单节点边界要小心避免重复。
五、右边界为什么要逆序
逆时针遍历时,右边界是从底部往根方向回来的。如果你从根往下收集右边界,顺序是上到下,需要最后反转。
右边界向下收集:3,6
边界输出需要:6,3
实现上可以先放入临时数组,最后倒序追加;也可以递归返回时追加。
六、边界情况如何处理
空树返回空;单节点树只输出根;没有左子树时,左边界为空;没有右子树时,右边界为空。根如果是叶子,不应该在根和叶子阶段输出两次。
| 边界情况 | 正确处理 |
|---|---|
| 空树 | 返回空列表 |
| 单节点树 | 只输出根一次 |
| 没有左子树 | 左边界阶段为空,叶子和右边界照常 |
| 没有右子树 | 右边界阶段为空,避免重复左侧叶子 |
记忆钩子:边界遍历不是一次 DFS 搞定的顺序题,而是“左边界、叶子、右边界”三段拼轮廓,叶子单独管去重。
七、常见误区与追问
- 误区:左边界和右边界都包含叶子。 这样叶子阶段会重复输出。
- 误区:右边界按从上到下追加。 逆时针边界需要右边界从下到上,所以要反转。
- 误区:左边界只能走 left。 没有左孩子时,右孩子也可能是外轮廓。
- 追问:单节点树输出什么? 只输出根一次,不能根阶段和叶子阶段重复。
- 追问:复杂度是多少? 每个节点最多访问常数次,时间 O(n),额外空间取决于结果和右边界临时栈。
八、加强记忆
边界遍历就像沿树的外墙走一圈:先从根走左墙,再扫底部叶子,最后从右墙底部回到上面。三段分开处理是为了顺序清楚和避免重复,尤其要记住左右边界都不收叶子。