二叉树的遍历系列(非递归)


二叉树的遍历:前序,中序,后序

(前,中,后,都是以根节点的访问顺序为参照。前序即为:根节点在最前;中序即为:根节点在中间;后序即为:根节点在最后。)

先上非常简单的递归版

   // 前序: 根 - 左 - 右
   private void dfs(TreeNode root, List<Integer> ans) {
        if (root == null) return ;
        ans.add(root.val); // 先访问根节点
        dfs(root.left, ans); // 递归的访问左子树
        dfs(root.right, ans); // 递归的访问右子树
    }

   // 中序: 左 - 根 - 右
   private void dfs(TreeNode root, List<Integer> ans) {
        if (root == null) return ;
        dfs(root.left, ans); // 递归的访问左子树
        ans.add(root.val); // 访问根节点
        dfs(root.right, ans); // 递归的访问右子树
    }

   // 后序: 左 - 右 - 根
   private void dfs(TreeNode root, List<Integer> ans) {
        if (root == null) return ;
        dfs(root.left, ans); // 递归的访问左子树
        dfs(root.right, ans); // 递归的访问右子树
        ans.add(root.val); // 访问根节点
    }

用递归的方式来写,代码非常简单,也很容易理解。递归的精妙就在于此,我们只需要想清楚递归函数的定义,然后从逻辑上完成它,剩下的工作,就全部交给计算机去处理了。递归函数,其实对我们开发者来说,很多时候都像是一个黑盒子,我们其实并不太容易想的明白,它的每一步具体都是怎么执行的。

为了深入理解计算机是如何执行递归方法的,我们还需要掌握二叉树的非递归遍历。也就是,我们需要人为的使用,来完成计算机自动帮我们完成的操作。

前序遍历

前序遍历是模拟起来最简单的。

写法一:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        // 先把根节点入栈
        if (root != null) stack.push(root);
        // 当栈不空时, 循环
        while (!stack.isEmpty()) {
            TreeNode x = stack.pop(); // 弹出栈顶, 并直接访问
            ans.add(x.val);
            if (x.right != null) stack.push(x.right); // 右儿子最后访问, 先入栈
            if (x.left != null) stack.push(x.left); // 左儿子先访问, 后入栈
        }
        return ans;
    }
}

写法二:(此种风格,可以统一地应用在中序,后序遍历上)

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        // root为当前处理的节点
        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                ans.add(root.val); // 直接访问
                stack.push(root); // 入栈做保存
                root = root.left; // 更新当前节点为左儿子
            }
            TreeNode x = stack.pop();
            if (x.right != null) root = x.right;
        }
        return ans;
    }
}
中序遍历

中序遍历,由于根节点不是最先访问的,最先访问的是左儿子,所以先一头扎下去,扎到整个左子树的最左侧。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();

        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                stack.push(root); // 先把当前节点入栈
                root = root.left;
            }
            // 当这个节点没有左儿子时, 访问这个节点, 这个节点是当前栈顶
            TreeNode x = stack.pop();
            ans.add(x.val);
            if (x.right != null) root = x.right;
        }
        return ans;
    }
}
后序遍历

根是最后访问的,这种模拟起来最困难。通常是先得到逆序,然后翻转。

写法一:(对前序遍历的写法一稍加修改)

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        if (root != null) stack.push(root);

        while (!stack.isEmpty()) {
            TreeNode x = stack.pop();
            ans.add(x.val);
            if (x.left != null) stack.push(x.left);
            if (x.right != null) stack.push(x.right);
        }
        // 反转顺序
        Collections.reverse(ans);
        return ans;
    }
}

写法二:(统一风格的写法)

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();

        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                ans.add(root.val);
                stack.push(root);
                root = root.right;
            }
            TreeNode x = stack.pop();
            if (x.left != null) root = x.left;
        }

        Collections.reverse(ans);
        return ans;
    }
}

写法三:(双栈)(仔细观察一下其实和写法一是一样的,这种写法是用一个额外的栈来做逆序而已)

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        Deque<TreeNode> temp = new LinkedList<>();
        if (root != null) temp.push(root);

        while (!temp.isEmpty()) {
            TreeNode x = temp.pop();
            stack.push(x);
            if (x.left != null) temp.push(x.left);
            if (x.right != null) temp.push(x.right);
        }
        while (!stack.isEmpty()) {
            TreeNode x = stack.pop();
            ans.add(x.val);
        }
        return ans;
    }
}

写法四:不逆序,需要一个额外的变量来存储前一个已访问的节点

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode pre = null; // 前一个已经处理过的节点

        while (root != null || !stack.isEmpty()) {
            while (root != null) {
                stack.push(root);
                root = root.left;
            }
            // 先一头扎到最左侧, 此时栈顶是最左侧的节点, 看该节点
            TreeNode x = stack.pop();
            // 如果该节点不存在右儿子, 或者上一个访问的节点是其右儿子, 则说明该访问此节点了
            if (x.right == null || pre == x.right) {
                ans.add(x.val);
                pre = x;
                root = null;
            } else {
            // 否则, 当前节点还无法访问, 重新入栈, 并处理其右儿子
                stack.push(x);
                root = x.right;
            }
        }
        return ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值