二叉树遍历要解决的问题只有一个:按照什么顺序处理当前节点、左子树和右子树。
左右子树本身也是二叉树,所以可以用同一个函数继续处理。这就是递归。前序、中序和后序并非三种不同思想,只是访问当前节点的时机不同。
一、遍历顺序由访问位置决定
对于任意节点,递归框架都是:
void traverse(TreeNode root) {
if (root == null) {
return;
}
// 位置一
traverse(root.left);
// 位置二
traverse(root.right);
// 位置三
}
访问操作放在不同位置,就形成三种遍历:
前序:根 → 左 → 右
中序:左 → 根 → 右
后序:左 → 右 → 根
对应代码如下:
// 前序遍历
void preorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
result.add(root.value);
preorder(root.left, result);
preorder(root.right, result);
}
// 中序遍历
void inorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
inorder(root.left, result);
result.add(root.value);
inorder(root.right, result);
}
// 后序遍历
void postorder(TreeNode root, List<Integer> result) {
if (root == null) {
return;
}
postorder(root.left, result);
postorder(root.right, result);
result.add(root.value);
}
所以,“前、中、后”描述的是根节点相对于左右子树的访问位置:
进入节点时访问 → 前序
左子树处理完后访问 → 中序
左右子树都处理完后访问 → 后序
二、调用栈如何产生遍历顺序
重点理解:
- 栈存储的是调用位置。
- 梳理清楚前、中、后遍历时,各元素是如何压栈的。
使用下面的二叉树:
1
/ \
2 3
/ \ / \
4 5 6 7
递归调用时,每进入一个节点,就把该节点的函数压入调用栈。子函数执行期间,父函数暂停;子函数返回后,父函数从暂停位置继续。
需要区分:
压栈:进入函数
访问:处理节点
出栈:函数执行完毕
访问节点不等于函数出栈。
1. 前序遍历
前序遍历进入节点后立即访问,因此压栈路径直接形成访问顺序。
进入并访问 1
栈顶
┌──────────────┐
│ 1:等待左子树 │
└──────────────┘
栈底
输出:1
进入并访问 2
栈顶
┌──────────────┐
│ 2:等待左子树 │
├──────────────┤
│ 1:等待左子树 │
└──────────────┘
栈底
输出:1 2
进入并访问 4
栈顶
┌──────────────┐
│ 4:正在执行 │
├──────────────┤
│ 2:等待左子树 │
├──────────────┤
│ 1:等待左子树 │
└──────────────┘
栈底
输出:1 2 4
4 完成后出栈,再进入 5;2 完成后出栈,再进入 3、6、7。
完整访问顺序:
1 → 2 → 4 → 5 → 3 → 6 → 7
2. 中序遍历
中序遍历先进入左子树,因此开始时只压栈,不访问。
依次进入 1、2、4
栈顶
┌──────────────┐
│ 4:等待左子树 │
├──────────────┤
│ 2:等待左子树 │
├──────────────┤
│ 1:等待左子树 │
└──────────────┘
栈底
输出:空
4 的左子树为空,所以访问 4 并返回。随后恢复 2,访问 2,再进入右子树 5。
栈顶
┌──────────────┐
│ 5:正在执行 │
├──────────────┤
│ 2:等待右子树 │
├──────────────┤
│ 1:等待左子树 │
└──────────────┘
栈底
输出:4 → 2
5 完成后,2 才能出栈。随后访问 1,再以相同方式处理右子树。
完整访问顺序:
4 → 2 → 5 → 1 → 6 → 3 → 7
中序遍历的栈中保存的是:左子树尚未完成,或者右子树尚未处理的节点。
3. 后序遍历
后序遍历必须等左右子树都完成后,才能访问当前节点。
依次进入 1、2、4
栈顶
┌──────────────┐
│ 4:等待子树 │
├──────────────┤
│ 2:等待左子树 │
├──────────────┤
│ 1:等待左子树 │
└──────────────┘
栈底
输出:空
4 没有子树,因此访问并出栈。接着进入并访问 5。此时 2 的左右子树都已完成,才访问 2。
栈顶
┌────────────────┐
│ 2:左右均已完成 │
├────────────────┤
│ 1:等待左子树 │
└────────────────┘
栈底
输出:4 → 5 → 2
右子树同理,依次访问 6、7、3。最后,节点 1 的左右子树都完成,访问 1。
完整访问顺序:
4 → 5 → 2 → 6 → 7 → 3 → 1
后序遍历体现的是依赖关系:必须先得到左右子树的结果,才能处理当前节点。
三、完整实现与复杂度
import java.util.ArrayList;
import java.util.List;
public class BinaryTreeTraversal {
static class TreeNode {
int value;
TreeNode left;
TreeNode right;
TreeNode(int value) {
this.value = value;
}
}
// 前序:根 -> 左 -> 右
static void preorder(
TreeNode root,
List<Integer> result) {
if (root == null) {
return;
}
result.add(root.value);
preorder(root.left, result);
preorder(root.right, result);
}
// 中序:左 -> 根 -> 右
static void inorder(
TreeNode root,
List<Integer> result) {
if (root == null) {
return;
}
inorder(root.left, result);
result.add(root.value);
inorder(root.right, result);
}
// 后序:左 -> 右 -> 根
static void postorder(
TreeNode root,
List<Integer> result) {
if (root == null) {
return;
}
postorder(root.left, result);
postorder(root.right, result);
result.add(root.value);
}
public static void main(String[] args) {
/*
* 1
* / \
* 2 3
* / \ / \
* 4 5 6 7
*/
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.left = new TreeNode(6);
root.right.right = new TreeNode(7);
List<Integer> preorderResult = new ArrayList<>();
List<Integer> inorderResult = new ArrayList<>();
List<Integer> postorderResult = new ArrayList<>();
preorder(root, preorderResult);
inorder(root, inorderResult);
postorder(root, postorderResult);
System.out.println("前序遍历:" + preorderResult);
System.out.println("中序遍历:" + inorderResult);
System.out.println("后序遍历:" + postorderResult);
}
}
输出:
前序遍历:[1, 2, 4, 5, 3, 6, 7]
中序遍历:[4, 2, 5, 1, 6, 3, 7]
后序遍历:[4, 5, 2, 6, 7, 3, 1]
每个节点只被处理一次,因此:
时间复杂度:O(n)
空间复杂度:O(h)
其中,n 是节点数量,h 是树高。空间消耗来自递归调用栈。
最终可以将三种遍历统一为一句话:
递归负责处理左右子树,访问根节点的位置决定遍历类型:访问在左递归之前是前序,在左右递归之间是中序,在右递归之后是后序。
402

被折叠的 条评论
为什么被折叠?



