算法题解记录5+++二叉树中序遍历(百日筑基)

题目描述:

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

解题准备:

        其实这道题的代码很简单,可以说是非常基础,但是正因为流传广、基础,很多人都不了解其原理,在此大致解释一下。【本人也是新手】

        1.了解二叉树:二叉树是树状结构中比较特殊的一种,从根节点看,它有且只有左子树和右子树,从每一个节点看,同样是有且仅有左右子树(左子树为null、右子树为null也看成拥有子树),具有迭代性。

        2.了解基础操作:明显,对于树,仅涉及遍历(本质是查找),对于链表(因为要返回数据),只涉及添加add,难点只在于遍历。

        3.了解中序遍历:X序遍历,是以根节点为标准,中序则指根节点在中间,左子树在前,右子树在后。【同理,先序是中左右,后序是左右中,左一定在右前】

        4.模拟操作:

                中序遍历其实一共只有三步【迭代的三步】:1st.遍历左子树。2nd.读取根节点的值。3rd.遍历右子树。

                很容易发现,第1st步和第3rd步无法直接获取值,所以所有获取值的操作都落在第2nd步上,问题在于如何模拟这种操作。

图1

解题难点1分析:

        经上分析,难点1:如何遍历左子树、右子树?

        【例子的基础,是图1】从二叉树的迭代性可知,root是一棵树,root.left也可以看成一棵二叉树。

        比如:1是一棵树【以1为根,左子为2,右子为4】,其左子节点2也是一棵树【以2为根,左子为3,右子为null】,同理,1的右子节点4也是一棵树【以4为根,左子为5,右子为6】

        注:树x是指,以x值为根的树【比如树2表示2为根的树】

        对于第一步

        对图1的树中序遍历,第一步是遍历左子树,也就是树2;然而,树2是树,那么中序遍历依旧要求遍历左子树,也就是树3;树3也是树,同理……

        这样下去,就无穷无尽,并且由于树3的左子树为null,还会有访问出错的问题。

        解决思路:提供结束条件。

        迭代左子树,明显地,当左子树为null时,就不用遍历左子树了,直接到第2步:得到本节点的值。

        这里其实就是编码最困难的地方:该用root.left?还是root作为结束条件?【这个问题的本质,是如何遍历出具有迭代性的代码】

        依下面的伪代码,如果root.left==null时,比如树3,无法使节点3入栈,也就是说,需要在遍历结束后,提供代码得到节点3的值(其实可以发现,依照伪代码,入栈的while完成后,一定是根节点的最左子孙节点)

        只是有两个问题:1.对于最左子孙节点单独处理,不符合迭代性质。

        2.将来遍历到第3步---右子树时,如何保证能单独处理最左子孙节点?

// 伪代码
// 得到左边的数据(把右子树视为null)
while(root.left!=null){
    stack.push(root); // 把节点入栈
    root=root.left;
}
    System.out.println(root.value);
// 遍历栈中数据
while(!stack.isEmpty()){
    System.out.println(stack.pop().value);
}

        所以,伪代码应该改为:

        不过有人会担心:如果左子树为null,岂不是访问溢出?其实null 表示没有值,但是内存为其分配了空间,如果是null.left(访问一个未分配空间的地址),才会出现异常。

        这样子,当访问到树3时,会把节点3入栈,然后访问树3左子树null(不满足!=null,进入下一步),此时栈中有123(由底往上),出栈后先是3,后是2,最后是1。

// 伪代码
// 得到左边的数据(把右子树视为null)
while(root!=null){
    stack.push(root); // 把节点入栈
    root=root.left;
}
// 遍历栈中数据
while(!stack.isEmpty()){
    System.out.println(stack.pop().value);
}

        至此,访问左子节点的任务就完成了。

        (root非空,是指新树来临时,栈中无数据)简化:

// 伪代码
// 得到左边的数据(把右子树视为null)
while( root!=null || !stack.isEmpty() ){
    while(root!=null){
        stack.push(root); // 把节点入栈
        root=root.left;
    }
    System.out.println(stack.pop().value);
}

解题难点2分析:

        虽然左子节点OK了,但是无论是根节点、还是沿途得到的子树,都没有访问过其右子树。

        所以问题1:如何访问右子树?

        由上可知,右子树也是一棵二叉树,比如树1的右子树树4,它也是二叉树,访问树4时,也要先访问左子树,再根节点,再右子树。

        由于左子节点的访问OK,所以问题在于:如何把右子树转化为节点?【这句话的意思是:怎么把右子节点入栈】

        对于图1,我们在访问树3时,其实是进行了三步的,只是其左右子树都为null,所以不处理,然而,如果树3有一右子树R,怎么办?

        这时候,就要考虑,得到节点3的值后,要遍历树R。

        伪代码如下:

        不过运行后会发现超出内存限制了(其实while变成死循环了)

// 伪代码
// 得到左边的数据(把右子树视为null)
while( root!=null || !stack.isEmpty() ){
    while(root!=null){
        stack.push(root); // 把节点入栈
        root=root.left;
    }
    // 到达root的最左子孙节点
    root=stack.pop(); // 树root的最左子孙节点
    System.out.println(root.value);
    // null没什么好讲的,非null就得遍历右子树
    if(root.right!=null){
        root=root.right;
    }    
}

        问题1:为什么会变成死循环?

        易得,如果root!=null || stack非空,则进行循环,而root=pop(),如果依照图1,那么在第一次while入栈完成后,栈中有123(由底到顶),赋值后root=3,栈中12,树3无右子树,所以继续外层while循环,while入栈后,栈中有123(由底到顶)……

        那么,怎么办呢?

        我们知道,遍历有三步,对于树1来说,先遍历树2。

        而遍历树2,就有先遍历树3【树x都同理】。

        对于树2遍历,有左中右的顺序,然后到其父节点也就是节点1。

        因此,如果遍历完右子树,就应该到父节点。

        问题2:如何到父节点?

        从while入栈循环可以得到,这一步一定会把左侧的所有节点,按照父子形式入栈(从底到顶,123分别是根--根左子--根左孙---根左……)

        那么,根左子树遍历完,就应该到根,所以stack.pop()没有问题。

        可是,这里由于右子树未遍历到的问题,使栈中元素死循环了。

        所以,如果根树比如树3右子为null,应该要使root=null,这样子能避免重复,同时使栈中的下一个元素可以被访问。

        由于当右子树为null时,root=null,并且右子树非空时,root=root.right。

        所以可以复用,无需设限,直接按以下写:

// 伪代码
// 得到左边的数据(把右子树视为null)
while( root!=null || !stack.isEmpty() ){
    while(root!=null){
        stack.push(root); // 把节点入栈
        root=root.left;
    }
    // 到达root的最左子孙节点
    root=stack.pop(); // 树root的最左子孙节点
    System.out.println(root.value);
    // null没什么好讲的,非null就得遍历右子树
    root=root.right;
}

代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        Stack<TreeNode> temp = new Stack<TreeNode>();

        while( !temp.isEmpty() || root!=null ){
            while(root!=null){
                temp.push(root);
                root=root.left;
            }
            root=temp.pop();
            result.add(root.val);
            root=root.right;
        }
        return result;
    }
}

以上内容即我想分享的关于力扣热题5的一些知识。

        我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值