力扣热门100题——二叉树的中序遍历(递归,迭代,Morris 中序遍历)

7、二叉树的中序遍历

1.问题描述

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

2.示例

示例 1:

输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:

输入:root = []
输出:[]
示例 3:

输入:root = [1]
输出:[1]

3.提示

树中节点数目在范围 [0, 100] 内
-100 <= Node.val <= 100

4.进阶

递归算法很简单,你可以通过迭代算法完成吗?

5.具体解法(递归,迭代,Morris 中序遍历)

//方法一:递归
//因为要返回一个遍历的结果,那么得要一个存储的容器,我们选择了ArrayList(不限长度,内容用泛型确定为Integer类型),示例的输出就是一个集合
//采用递归的方法,就得有一个方法去被递归,inorder,参数是结点和存储遍历内容的集合
//中序遍历是左,根,右,从根节点出发,如果根节点有左结点,就应该递归调用这个左子树的情况,直到这个此时迭代到的结点没有左子结点,把它存入集合中
//存完了之后,再看这个根节点的右子结点情况如何,不断迭代就可以了
class Solution {
    public List inorderTraversal(TreeNode root) {
        List res=new ArrayList<>();
        inorder(root,res);
        rerurn res;
    }
    public void inorder(TreeNode root,Listres){
        if(root==null){
            return;//这个return的效果就是,如果根节点为空,那么就应该不进行下面的操作,返回上一层调用
        }
        inorder(root.left,res);
        res.add(root.val);
        inorder(root.right,res);
    }
}
//方法二:迭代
//迭代就不用方法去递归调用了,而是用一个栈去实现
//递归实现时,是函数自己调用自己,一层层的嵌套下去,操作系统/虚拟机自动帮我们用 栈 来保存了每个调用的函数,现在我们需要自己模拟这样的调用过程。
/*
class Solution {
    public List inorderTraversal(TreeNode root) {
        List res = new ArrayList();
        Deque stk = new LinkedList();//创建一个栈
        while (root != null || !stk.isEmpty()) {//当根节点不为空或者栈不为空的时候,我们就应该去判断,如果是根节点不为空,那就往左子结点走
            while (root != null) {//直到左子结点是空了,就退出此while
                stk.push(root);
                root = root.left;
            }
            root = stk.pop();//将栈中最后存的那个结点取出,他就是整个树最左下角的结点,值放入集合中
            res.add(root.val);
            root = root.right;//然后将这个root变成右结点再去看对应这个层的右子树情况
        }
        return res;
    }
}

 */

//方法三:Morris 中序遍历
//用递归和迭代的方式都使用了辅助的空间,而莫里斯遍历的优点是没有使用任何辅助空间。缺点是改变了整个树的结构,强行把一棵二叉树改成一段链表结构。
//Morris 遍历算法是另一种遍历二叉树的方法,它能将非递归的中序遍历空间复杂度降为O(1)。
//Morris 遍历算法整体步骤如下(假设当前遍历到的节点为 x):

//如果x无左孩子,先将x的值加入答案数组,再访问x的右孩子,即x=x.right。
//如果x有左孩子,则找到x左子树上最右的节点(即左子树中序遍历的最后一个节点,x在中序遍历中的前驱节点),我们记为predecessor。
//根据predecessor的右孩子是否为空,进行如下操作。
//如果predecessor的右孩子为空,则将其右孩子指向x,然后访问x的左孩子,即x=x.left。
//如果predecessor的右孩子不为空,则此时其右孩子指向x,说明我们已经遍历完x的左子树,我们将predecessor的右孩子置空,将x的值加入答案数组,然后访问x的右孩子,即 x=x.right。
//重复上述操作,直至访问完整棵树。

//其实整个过程我们就多做一步:假设当前遍历到的节点为x,将x的左子树中最右边的节点的右孩子指向x,
//这样在左子树遍历完成后我们通过这个指向走回了x,且能通过这个指向知晓我们已经遍历完成了左子树,而不用再通过栈来维护,省去了栈的空间复杂度。
class Solution {
    public List inorderTraversal(TreeNode root) {
        List res = new ArrayList();
        TreeNode predecessor = null;

        while (root != null) {
            if (root.left != null) {
                // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
                predecessor = root.left;
                while (predecessor.right != null && predecessor.right != root) {
                    predecessor = predecessor.right;
                }

                // 让 predecessor 的右指针指向 root,继续遍历左子树
                if (predecessor.right == null) {
                    predecessor.right = root;
                    root = root.left;
                }
                // 说明左子树已经访问完了,我们需要断开链接
                else {
                    res.add(root.val);
                    predecessor.right = null;//树是不能构成一个回路的,用这个算法就是用回路来实现指回root进行访问然后遍历右子树,
                                             // 不然没办法回去;当不加这行代码的时候,可想这个二叉树已经不是二叉树了
                    root = root.right;
                }
            }
            // 如果没有左孩子,则直接访问右孩子
            else {
                res.add(root.val);
                root = root.right;
            }
        }
        return res;
    }
}

对于方法三的一个补充理解:

力扣热门100题——二叉树的中序遍历(递归,迭代,Morris 中序遍历)_第1张图片

我们将黄色区域部分挂到节点5的右子树上,接着再把2和5这两个节点挂到4节点的右边。
这样整棵树基本上就变改成了一个链表了,之后再不断往右遍历。

只要当前结点有左子节点,我们就把此结点和右子树都挂到左子结点的右下角,以此类推挂完了之后,再去遍历,而且遍历完每个左子树的时候,都要断开之前的一个挂的链接,恢复之前的形状

6.收获

  • 遍历这种问题,都需要有一个返回值,这个时候得想用什么存储,这个是需要自己想到的

  • 递归的话,就得写一个方法去递归,而且得有出口

  • 复习了二叉树的中序遍历

  • 定义 inorder(root) 表示当前遍历到 \textit{root}root 节点的答案,那么按照定义,我们只要递归调用 inorder(root.left) 来遍历 \textit{root}root 节点的左子树,然后将 \textit{root}root 节点的值加入答案,再递归调用inorder(root.right) 来遍历 \textit{root}root 节点的右子树即可,递归终止的条件为碰到空节点。(中序遍历的思路)

  • 递归遍历太简单了

    前序遍历:打印 - 左 - 右
    中序遍历:左 - 打印 - 右
    后序遍历:左 - 右 - 打印
    题目要求的是中序遍历,那就按照 左-打印-右这种顺序遍历树就可以了,递归函数实现

    终止条件:当前节点为空时
    函数内:递归的调用左节点,打印当前节点,再递归调用右节点

  • 对于树、链表等有了新认识,就是树,链表等这种数据结构,我们在实现的时候是通过一个类来实现的,里面有(以树为例)值,左节点,右节点,还有构造方法,这样其实是一个结点,而通过一个又一个结点,这样组成了我们想表达的树,在使用的时候也是利用这几个成员变量去分析。

  • 对于树、链表等有了新认识,就是树,链表等这种数据结构,我们在实现的时候是通过一个类来实现的,里面有(以树为例)值,左节点,右节点,还有构造方法,这样其实是一个结点,而通过一个又一个结点,这样组成了我们想表达的树,在使用的时候也是利用这几个成员变量去分析。

你可能感兴趣的:(力扣刷题,java,力扣,算法)