二叉树的递归遍历比较简单,但是在遍历节点较多的二叉树时有可能会栈溢出,所以还是非递归更安全
递归的本质还是栈,用栈模仿递归的过程即可
前序遍历
对于每个节点来说,先遍历节点值,再遍历左子树,最后遍历右子树
在递归解法中,每遍历到一个节点,首先将节点的当前值加入遍历结果,然后递归遍历左子树,左子树遍历结束后再遍历右子树
递归的本质是栈,使用一个递归栈模拟当前递归的深度,即使用栈存当前节点的上层节点,在当前节点遍历后弹出栈顶元素,即为上层节点,也等同于返回上层递归
具体的解题思路为
- 使用一个 List 存遍历结果,使用双向队列模拟递归栈,使用一个节点 node 表示当前层递归遍历的节点,初始时 node 即为根节点
- 若当前遍历节点不为空或者递归栈不为空,表示二叉树还未遍历结束,则需要继续遍历,进入下一步
- 每次遍历时,如果当前遍历节点不为空,按照先序遍历的规则,需要先遍历当前节点,然后遍历左子树。于是先将当前节点值放入遍历结果,然后将当前节点加入递归栈,更新当前节点为左儿子节点,即存入当前层递归后进入下一层递归
- 如果当前节点为空,表示当前节点已经遍历结束,返回递归上一层,即弹出栈顶元素,并更新当前节点为栈顶元素的右儿子(因为左儿子已经在上一步中遍历过)
- 重复 3、4 步,直至当前遍历节点为空且递归栈为空
public List<Integer> preorder(TreeNode root) {
// 存遍历结果
List<Integer> ans = new ArrayList<>();
// 递归栈
Deque<TreeNode> stack = new ArrayDeque<>();
// 当前待遍历的节点
TreeNode node = root;
// 当待遍历节点不为空且递归栈不为空时,表明二叉树还未遍历结束
while (!stack.isEmpty() || node != null) {
// 优先遍历当前节点和左子树
while (node != null) {
// 优先遍历当前节点
ans.add(node.val);
// 将当前节点存入递归栈
stack.push(node);
// 然后遍历左子树
node = node.left;
}
// 弹出递归栈顶元素,返回上一轮遍历节点的右子树
node = stack.pop().right;
}
return ans;
}
中序遍历
左子树 -> 本节点值 -> 右子树
中序遍历与前序遍历的区别即为先遍历左子树,在左子树递归结束后将当前节点加入已遍历节点,之后再遍历右子树
那么解题步骤与上一步的区别即为在递归遍历左子树时不放入当前节点的值到已遍历集合中,而是在当前节点左子树为空后才放入
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode node = root;
while (!stack.isEmpty() || node != null) {
while (node != null) {
// 当前节点入栈,类似存下递归的上层
stack.push(node);
// 优先遍历左子树
node = node.left;
}
// 左子树为空时,栈顶元素出栈,返回上层递归
node = stack.pop();
// 遍历上层递归节点值
ans.add(node.val);
// 最后遍历右子树
node = node.right;
}
return ans;
}
后序遍历
左子树 -> 右子树 -> 本节点值
后序遍历又是在中序遍历的基础上做修改,遍历的步骤为优先遍历左右子树才遍历当前节点,那么就需要在遍历左子树后,再遍历右子树,右子树遍历结束后才能遍历当前节点
那么如何判断右子树是否遍历?
可以通过标志位 pre 表示上次访问过的右儿子,如果 pre 为当前节点右儿子,即表示当前节点右子树已遍历,可以遍历当前节点
那么解题步骤为:
- 使用一个 List 存遍历结果,使用双向队列模拟递归栈,使用一个节点 node 表示当前层递归遍历的节点,初始时 node 即为根节点,再使用一个 pre 表示上次访问过的右儿子
- 若当前遍历节点不为空或者递归栈不为空,表示二叉树还未遍历结束,则需要继续遍历,进入下一步
- 每次遍历时,如果当前遍历节点不为空,按照后序遍历的规则,需要先遍历左子树。于是将当前节点加入递归栈,更新当前节点为左儿子节点,即存入当前层递归后进入下一层递归
- 如果当前节点为空,表示当前节点已经遍历结束,返回递归上一层,即弹出栈顶元素。此时判断节点右儿子是否为空且是否为 pre,如果是则表示没有右子树或者右子树已经遍历,遍历当前节点,并将当前节点置为空(防止重复进入左子树,可以理解为当前层递归遍历结束,当前节点为空,返回上层);否则表示右子树还未遍历,将当前节点放入递归栈后更新为右子树,优先遍历右子树
- 重复 3、4 步,直至当前遍历节点为空且递归栈为空
public List<Integer> postorder(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode node = root;
TreeNode pre = null; // 上次遍历的右儿子
while (!stack.isEmpty() || node != null) {
// 递归遍历左子树
while (node != null) {
stack.push(node);
node = node.left;
}
// 返回递归上一层
node = stack.pop();
// 如果上层节点右儿子不为空,且右儿子不是最近访问过的(即有可能是刚遍历过右子树返回当前节点)
// 优先遍历右子树
if (node.right != null && node.right != pre) {
// 存下当前节点入递归栈
stack.push(node);
// 更新当前节点为右儿子
node = node.right;
} else { // 否则,表示右子树为空或者右儿子遍历过,遍历当前节点
pre = node;
ans.add(node.val);
// 当前节点置空,防止重复进入左子树
node = null;
}
}
return ans;
}