二叉树遍历--非递归

本文详细介绍了如何使用非递归方式实现二叉树的前序、中序和后序遍历,通过模拟递归栈避免了栈溢出的问题。在前序遍历中,先遍历节点值,再遍历左子树,然后右子树;中序遍历顺序为左子树、节点值、右子树;后序遍历则是左子树、右子树、节点值。通过双向队列和节点状态跟踪,可以有效地实现这些遍历方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二叉树的递归遍历比较简单,但是在遍历节点较多的二叉树时有可能会栈溢出,所以还是非递归更安全

递归的本质还是栈,用栈模仿递归的过程即可

前序遍历

对于每个节点来说,先遍历节点值,再遍历左子树,最后遍历右子树

在递归解法中,每遍历到一个节点,首先将节点的当前值加入遍历结果,然后递归遍历左子树,左子树遍历结束后再遍历右子树

递归的本质是栈,使用一个递归栈模拟当前递归的深度,即使用栈存当前节点的上层节点,在当前节点遍历后弹出栈顶元素,即为上层节点,也等同于返回上层递归

具体的解题思路为

  1. 使用一个 List 存遍历结果,使用双向队列模拟递归栈,使用一个节点 node 表示当前层递归遍历的节点,初始时 node 即为根节点
  2. 若当前遍历节点不为空或者递归栈不为空,表示二叉树还未遍历结束,则需要继续遍历,进入下一步
  3. 每次遍历时,如果当前遍历节点不为空,按照先序遍历的规则,需要先遍历当前节点,然后遍历左子树。于是先将当前节点值放入遍历结果,然后将当前节点加入递归栈,更新当前节点为左儿子节点,即存入当前层递归后进入下一层递归
  4. 如果当前节点为空,表示当前节点已经遍历结束,返回递归上一层,即弹出栈顶元素,并更新当前节点为栈顶元素的右儿子(因为左儿子已经在上一步中遍历过)
  5. 重复 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 为当前节点右儿子,即表示当前节点右子树已遍历,可以遍历当前节点

那么解题步骤为:

  1. 使用一个 List 存遍历结果,使用双向队列模拟递归栈,使用一个节点 node 表示当前层递归遍历的节点,初始时 node 即为根节点,再使用一个 pre 表示上次访问过的右儿子
  2. 若当前遍历节点不为空或者递归栈不为空,表示二叉树还未遍历结束,则需要继续遍历,进入下一步
  3. 每次遍历时,如果当前遍历节点不为空,按照后序遍历的规则,需要先遍历左子树。于是将当前节点加入递归栈,更新当前节点为左儿子节点,即存入当前层递归后进入下一层递归
  4. 如果当前节点为空,表示当前节点已经遍历结束,返回递归上一层,即弹出栈顶元素。此时判断节点右儿子是否为空且是否为 pre,如果是则表示没有右子树或者右子树已经遍历,遍历当前节点,并将当前节点置为空(防止重复进入左子树,可以理解为当前层递归遍历结束,当前节点为空,返回上层);否则表示右子树还未遍历,将当前节点放入递归栈后更新为右子树,优先遍历右子树
  5. 重复 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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三更鬼

谢谢老板!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值