【LeetCode - Java】94. 二叉树的中序遍历 (简单)

1. 题目描述

在这里插入图片描述

2. 解题思路

二叉树无论是前序、中序还是后序遍历,利用递归都是可以很简单地实现并且通俗易懂的。在本道题目中使用迭代算法实现才是有挑战性的任务。递归是使用了,那么毫无疑问使用迭代算法时需要人为地构造栈结构,那么难点在于什么部分呢?其难点正是在于出栈入栈的规则设定,实不相瞒,昨晚想了一个多小时愣是卡住了没想出来,今早吃完个早餐突然就有了想法了,这可能就是心理学当中的酝酿效应吧。

言归正传,对于中序遍历而言,“先解决左子树,再解决自己,后解决右子树” 是它的基本规则,而根据栈先进后出的特点,压栈顺序就应该是逆着来先压右子树,再压自己,然后处理左子树(开启新的一次迭代)

那么我究竟是卡在了哪一个点上呢?

按照上面的逻辑,在每一轮迭代中,都应该判断当前节点是否有右子树,有则压栈;然后判断是否有左子树,有则把自己压栈,开始新一轮迭代处理左子树,若没有左子树,则把自己添加到结果中,从栈中弹一个节点在新一轮迭代中处理。

逻辑上好像没问题,但是,我怎么知道现在这一轮迭代中的节点有没有已经访问过呢?

因为倘若该节点是有左子树的话,该节点是先被访问,然后判断有左子树,然后被压栈,然后处理左子树完毕后,再弹栈把该节点取出。问题在于弹栈取出后,便会开启新的迭代,又会重新判断是否有左孩子,这不就死循环了?

因此应该在发现有左孩子后,取出左孩子,然后把该节点的左孩子赋空再进行压栈 (相当于做了一个标记表示该节点的左子树部分已经处理完毕了)

说起来有点乱,可以了看看整体逻辑的总结版,设当前遍历的节点为root
在这里插入图片描述

提交完答案瞅了一眼官解,好家伙,还有个Morris算法,是我见识太少,它的思路,是这样的:
在这里插入图片描述
简洁版表示,是这样的:
在这里插入图片描述
有点绕,不过看懂了之后发现总体思路确实很妙!

一开始不太理解predecessor右孩子不为空的什么情况,定义不是表示左子树的最右节点吗,怎么可能不为空?

认真想了一下发现,噢!不为空的情况就是已经与x建立了链接的情况,即下图所示:
在这里插入图片描述
既然不太好理解,那我改一改本质还是在于判断该节点(x)的左子树有没有处理完毕嘛,那么我可以在predecessor的右孩子指向x之后,把x左孩子赋空值,那就可以达到同样的效果了!

算法就改成了这个逻辑
在这里插入图片描述
树结构则大致变成了这样:
在这里插入图片描述

3. 代码实现

3.1 递归

public List<Integer> inorderTraversal(TreeNode root) {
        if(root!=null){
            ArrayList<Integer> integers = new ArrayList<>();
            integers.addAll(inorderTraversal(root.left));
            integers.add(root.val);
            integers.addAll(inorderTraversal(root.right));
            return integers;
        }
        return new ArrayList<>();
    }

在这里插入图片描述

3.2 迭代

public List<Integer> inorderTraversal(TreeNode root) {
        if(root==null)
            return new ArrayList<Integer>();
        List<Integer> integers = new ArrayList<Integer>();
        LinkedList<TreeNode> treeNodes = new LinkedList<TreeNode>();
        treeNodes.add(new TreeNode(-101));
        while (root.val != -101) {
            if (root.right != null) {
                treeNodes.add(root.right);
                root.right = null;
            }
            if (root.left != null) {
                TreeNode left = root.left;
                root.left = null;
                treeNodes.add(root);
                root=left;
            } else {
                integers.add(root.val);
                root = treeNodes.removeLast();
            }
        }
        return integers;
    }

3.3 改进Morris算法

public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> integers = new ArrayList<>();
        TreeNode treeNode = null;
        while (root != null) {
            if (root.left == null) {
                integers.add(root.val);
                root = root.right;
            } else {
                treeNode = root.left;
                while (treeNode.right != null) {
                    treeNode = treeNode.right;
                }
                treeNode.right = root;
                treeNode = root.left;
                root.left = null;
                root = treeNode;
            }
        }
        return integers;
    }

在这里插入图片描述

3.4 对比

三种算法的时间复杂度都是O(n),最大的区别的空间复杂度部分,递归算法和普通迭代算法的空间复杂度最坏的情况下都是O(n),在递归中是自动实现的栈结构,而在迭代中需要自行构造栈结构,但总体而言在遍历逻辑上是相同的。而著名的Morris算法利用巧妙的思想,改变了遍历所需要的空间复杂度为O(1),使其在遍历过程中不再需要依靠栈实现,本质上其实是改变了二叉树的空间结构
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值