【二叉树Java】二叉树遍历前序中序后序遍历的非递归写法

本文主要介绍二叉树前序中序后序遍历的非递归写法

在探讨如何写出二叉树的前序中序后序遍历代码之前,我们先来明确一个问题,前序中序后序遍历根据什么区分

二叉树的前序中序后序遍历,是相较根节点说的。最先遍历根节点即为前序遍历,第二遍历根节点即为中序遍历,最后遍历根节点为后序遍历。左右节点的遍历顺序都是先左后右。

明确了上述之后,我们再来看一下如何定义树节点这一数据结构,新建树节点类,其具有值,左子树指针,右子树指针三种属性,加上构造方法后定义出来的结构如下:

    // 树结构
    public class TreeNode{
        int val;
        TreeNode left;
        TreeNode right;

        public TreeNode(){

        }

        public TreeNode(int val, TreeNode left, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }

明确了上述之后,让我们来一起看下二叉树前序遍历、中序遍历、后序遍历的非递归写法吧

1. 前序遍历

我们都知道递归底层是用栈来操作,那我们的非递归写法也就是使用栈模拟出二叉树遍历的操作。前序遍历使用栈模拟的操作过程如下:

在这里插入图片描述

每次执行的操作为:根节点出栈,节点值加入结果数组,然后将根节点右子节点入栈,再将根节点左子节点入栈。循环往复,直至栈为空。

先操作根节点值我们大家都能理解,那为什么是右子节点先入栈,然后左子节点后入栈那?

栈的操作为先进后出,因此先入栈的右子节点后出栈,后入栈的左子节点先出栈,这样得到的遍历顺序就为 —— 中 、左、右,因此要先入栈右子节点,后入栈左子节点。

代码:
    // 二叉树先序遍历迭代写法
    public List<Integer> preorderTraversal(TreeNode root){
        // 存储遍历结果
        List<Integer> ansList = new ArrayList<>();

        if(root == null){ // 树为空情况处理
            return ansList;
        }

        // 用于模拟操作的栈
        Stack<TreeNode> stack = new Stack<>();

        // 初始化放入根节点
        stack.push(root);
        while(!stack.isEmpty()){
            // 先序遍历先处理根节点
            TreeNode node = stack.pop();
            ansList.add(node.val);
            // 加入右节点 —— 先加入右节点后加入左节点 —— 从而保证出栈顺序为先左再右
            if(node.right != null) stack.push(node.right);
            // 加入左节点
            if(node.left != null) stack.push(node.left);
        }
        return ansList;
    }

2. 后序遍历

如果你理解了二叉前序遍历的非递归写法,那么后序遍历的非递归写法,就可以很快写出了。我们来思考一下前序遍历和后序遍历的关系。前序遍历的遍历顺序为 中左右,我们将其结果翻转一下就变成了 右左中,而后序遍历的顺序为 左右中,因此只需要把前序遍历的代码稍微调整一下左右子节点入栈的顺序,最后再把结果数组翻转就能得到后序遍历的写法了。具体操作为左节点先入栈,右节点后入栈(与我们在前序遍历中的入栈顺序正好相反),最后再使用 Collections.reverse() API 将结果List 翻转。

代码:
    // 后序遍历迭代写法 —— 思考其和前序遍历的不同 —— 前序遍历是 中左右  , 将其变为 中右左 ,再翻转最后的结果数组就变为 左右中 ,刚刚好是后序遍历的结果
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ansList = new ArrayList<>();
        if(root == null){
            return ansList;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){ // 出栈顺序为 中右左
            TreeNode node = stack.pop();
            ansList.add(node.val);
            if(node.left != null) stack.push(node.left);
            if(node.right != null) stack.push(node.right);
        }
        Collections.reverse(ansList); // 翻转链表
        return ansList;
    }

3. 中序遍历

前序遍历之所以看起来简单,是因为其操作的节点和其遍历顺序相同。而对中序遍历我们的模拟方法为:先一直沿左子树遍历,将沿途的节点入栈,直到遍历到最左叶子节点,这时就可以开始我们的操作了,操作方法为将最左叶子节点出栈进行操作,然后将右子节点入栈,右子节点重复执行上述过程(即找到右子节点的最左子节点…省略重复过程…)。

具体到代码: 因为不是最先遍历root 节点了,因此不需要先在 stack 中加入 root。而是使用一 cur 节点记录当前遍历的节点,开始的时候 cur 一直向左找,直到找到第一个要操作的节点,cur 和 stack 其中一个不为空就继续遍历。

代码:
   // 二叉树中序遍历迭代写法
    public List<Integer> inorderTraversal(TreeNode root) {
        // 存储遍历结果
        List<Integer> ansList = new ArrayList<>();

        if(root == null){ // 树为空情况处理
            return ansList;
        }

        // 用于模拟操作的栈
        Stack<TreeNode> stack = new Stack<>();

        /********** 上述内容所有遍历都一样 ************/

        TreeNode cur = root;

        while(cur!=null || !stack.isEmpty()){
            if(cur != null){ // 一直放左子节点,直到找到要处理的第一个左子节点
                stack.push(cur);
                cur = cur.left; // 将当前遍历节点变为左子节点
            } else{ // 到达要处理的位置
                TreeNode node = stack.pop();
                ansList.add(node.val); // 处理根节点 —— 先处理根节点,然后就加入右叶子节点
                cur = node.right; // 将当前遍历节点改为右子节点
            }
        }
        return ansList;

    }
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值