递归法原理与实现
递归法是解决树问题最直观的方法。其核心思想是将大问题分解为小问题:遍历一棵树等同于遍历其左子树和右子树。
1. 前序遍历 (Pre-order)
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
result.add(root.val); // 1. 访问根
result.addAll(preorderTraversal(root.left)); // 2. 遍历左
result.addAll(preorderTraversal(root.right)); // 3. 遍历右
return result;
}
2. 中序遍历 (In-order)
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
result.addAll(inorderTraversal(root.left)); // 1. 遍历左
result.add(root.val); // 2. 访问根
result.addAll(inorderTraversal(root.right)); // 3. 遍历右
return result;
}
3. 后序遍历 (Post-order)
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
result.addAll(postorderTraversal(root.left)); // 1. 遍历左
result.addAll(postorderTraversal(root.right)); // 2. 遍历右
result.add(root.val); // 3. 访问根
return result;
}
💡 记忆技巧:根节点的位置决定了遍历的名称。根在前是前序,根在中是中序,根在后是后序。
迭代法原理与实现
迭代法需要使用栈(Stack)来模拟递归调用栈的行为。对于前序遍历,由于根节点最先访问,我们可以直接将根节点压栈,然后依次处理右子节点和左子节点(因为栈是后进先出,所以右子节点要先压栈,左子节点后压栈,这样出栈时左子节点先被处理)。
1. 前序遍历迭代实现
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val); // 访问根
if (node.right != null) stack.push(node.right); // 右子节点先入栈
if (node.left != null) stack.push(node.left); // 左子节点后入栈
}
return result;
}
2. 中序遍历迭代实现
中序遍历的迭代实现稍复杂,因为根节点需要在左子树遍历完之后才访问。我们需要一直向左走,将路径上的节点压栈,直到左子节点为空,然后弹出栈顶节点访问,再转向其右子树。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
result.add(curr.val);
curr = curr.right;
}
return result;
}
3. 后序遍历迭代实现
后序遍历的迭代实现可以通过修改前序遍历的策略来完成。我们知道前序是“根->左->右”,如果我们改为“根->右->左”,然后将结果反转,就得到了“左->右->根”,即后序遍历。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null) stack.push(node.left); // 左子节点先入栈
if (node.right != null) stack.push(node.right); // 右子节点后入栈
}
Collections.reverse(result); // 反转结果
return result;
}
Morris遍历(空间复杂度O(1))
Morris遍历是一种利用叶子节点的空指针来存储临时信息,从而实现非递归且非栈的遍历方法。其核心思想是利用中序前驱节点。
- 时间复杂度:O(N),每个节点最多被访问两次。
- 空间复杂度:O(1),不需要栈或递归调用栈。
- 适用场景:对空间复杂度有极致要求的场景,但会修改树的结构(遍历后恢复)。
由于Morris遍历逻辑较为复杂,且在实际面试中考察频率低于前两种方法,此处仅提供思路说明。核心步骤包括:找到当前节点左子树的最右节点(前驱),如果前驱右指针为空,则将其指向当前节点并进入左子树;如果前驱右指针已指向当前节点,则说明左子树已遍历完,恢复指针,访问当前节点并进入右子树。