94. 二叉树的中序遍历

问题描述

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

示例1:
在这里插入图片描述

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

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

题目来源:LeetCode官方
链接地址:https://leetcode.cn/problems/binary-tree-inorder-traversal/

思路

这里我们主要提供三种解法:
1.递归
2.用栈辅助
3.标记法
题目虽然是求中序遍历,但我们可以把它扩展到先序与后序遍历,具体做法见代码

核心代码

递归:

    /**
     * 这个前,中,后是针对根节点而言的,递归实现
     *
     * @param root
     * @return
     */
    public static List<Integer> orderTraversal_dg(TreeNode root) {

        List<Integer> list = new ArrayList<>();
//        preorder(root, list);
//        inorder(root, list);
        postorder(root, list);
        return list;
    }

    //前序遍历,PLR
    //中序遍历,LPR
    //后序遍历,LRP
    public static void preorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
        if (root == null) return;

        list.add(root.val);
        preorder(root.left, list);//递归遍历左子树
        preorder(root.right, list);//递归遍历右子树

    }
    /**
     * 递归的核心部分
     * @param root
     * @param list
     */
    public static void inorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
        if (root == null) return;

        //在递归的作用下,程序会一直遍历到二叉树的最底层左叶子结点,然后将其值记录,然后回到上一层递归,记录根结点,再访问右结点
        inorder(root.left, list);//递归遍历左子树
        list.add(root.val);//前序和后续只需要将该语句放到相应位置即可
        inorder(root.right, list);//递归遍历右子树

    }
    /**
     * 递归的核心部分
     * @param root
     * @param list
     */
    public static void postorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
        if (root == null) return;

        postorder(root.left, list);//递归遍历左子树
        postorder(root.right, list);//递归遍历右子树
        list.add(root.val);

    }

代码解释

递归的核心是preorder、inorder和postorder三个方法,orderTraversal_dg通过调用这三个方法完成操作。list.add(root.val);语句的位置,决定了序列的输出

栈迭代:

    //前序遍历
    public static List<Integer> preTraversal_stack(TreeNode root) {

        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        //当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
        while(root != null || !stack.isEmpty()){
            //只要结点不空,我们就将其及其左结点入栈
            while (root != null){
                list.add(root.val);//将值加入到list中
                stack.push(root);
                root = root.left;
            }
            //入栈结束后,就可以将值加入到list中了
            root = stack.pop();//更新root
            root = root.right;//尝试看root有没有右结点,没有就将root置空,进入下一次循环
        }

        return list;
    }

    /**
     * 这里我们用栈来解决
     * @param root
     * @return
     */
    public static List<Integer> inorderTraversal_stack(TreeNode root) {

        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        //当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
        while(root != null || !stack.isEmpty()){
            //只要结点不空,我们就将其及其左结点入栈
            while (root != null){
                stack.push(root);
                root = root.left;
            }
            //入栈结束后,就可以将值加入到list中了
            root = stack.pop();//更新root
            list.add(root.val);//将值加入到list中
            root = root.right;//尝试看root有没有右结点,没有就将root置空,进入下一次循环
        }

        return list;
    }

    public static List<Integer> postTraversal_stack(TreeNode root) {

        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);//先将根结点入栈
        //当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
        while(!stack.isEmpty()){
            root = stack.pop();//出栈
            list.add(root.val);//将值加入到list中

            //只要结点不空,我们就将其及其左结点入栈
            if (root.left != null) stack.push(root.left);
            if (root.right != null) stack.push(root.right);

        }
        //因为以上述代码得到的list与我们想要的后序遍历输出顺序相反,所以这里进行逆序操作
        Collections.reverse(list);//使用Collections类里头的方法将list反转
        return list;
    }

代码解释

栈迭代的时间和空间优化是最好的,如果只考虑前序后中序,选栈迭代最佳,因为你只要记住了中序,前序只要调换位置就行,但如果是后序遍历,那么栈迭代就不太友好了,你会发现后序的代码与前序、中序差异较大,并不想递归那样好记

标记法:

//因为题目给的TreeNode结构体不包含flag,所以我们将flag和原本的TreeNode封装成Node
class Node{
    Boolean flag = false;//表示结点的访问状态,默认false,表示未访问
    TreeNode treenode;

    Node(TreeNode treenode){
        this.treenode = treenode;
    }
    Node(Boolean flag, TreeNode node) {
        this.flag = flag;
        this.treenode = node;
    }
}

//    使用flag标记节点的状态,新节点为false,已访问的节点为true。
//    如果遇到的节点为false,则将其标记为true,然后将其右子节点、自身、左子节点依次入栈。
//    如果遇到的节点为true,则将节点的值输出。
//----此法比递归效率高,相比于栈迭代逊色一些,但好处在于,只要掌握一种,就能像递归一样随意得到另外两种,只需要交换核心代码顺序即可
//----栈迭代法如果只是前序和中序,那还好,如果是后序,那代码改动范围太大,不太友好
    //前序遍历
    public static List<Integer> preTraversal(TreeNode root) {
        Stack<Node> stack = new Stack<>();//定义工具栈
        List<Integer> list = new ArrayList<>();
        Node roots = new Node(root);
        stack.push(roots);//将初始结点入栈
        //栈不空,加入循环
        while(!stack.isEmpty()){
            Node node = stack.pop();
            if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环

            if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
                //这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
                Node left = new Node(node.treenode.left);
                Node right = new Node(node.treenode.right);

                //改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
                //比如说这里是right,left,node入栈顺序,那么输出结果就是node,left,right,将node看成根结点
                stack.push(right);
                stack.push(left);
                node.flag = true;//修改访问轨迹
                stack.push(node);
            }else{
                list.add(node.treenode.val);
            }
        }
        return list;
    }

    /**
     * 标记法,false未访问,true已访问
     * @param root
     * @return
     */
    public static List<Integer> inorderTraversal(TreeNode root) {
        Stack<Node> stack = new Stack<>();//定义工具栈
        List<Integer> list = new ArrayList<>();
        Node roots = new Node(root);
        stack.push(roots);//将初始结点入栈
        //栈不空,加入循环
        while(!stack.isEmpty()){
            Node node = stack.pop();
            if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环

            if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
                //这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
                Node left = new Node(node.treenode.left);
                Node right = new Node(node.treenode.right);

                //改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
                //比如说这里是right,node,left入栈顺序,那么输出结果就是left,node,right,将node看成根结点
                stack.push(right);
                node.flag = true;//修改访问轨迹
                stack.push(node);
                stack.push(left);
            }else{
                list.add(node.treenode.val);
            }
        }
        return list;
    }

    //后序遍历
    public static List<Integer> postTraversal(TreeNode root) {
        Stack<Node> stack = new Stack<>();//定义工具栈
        List<Integer> list = new ArrayList<>();
        Node roots = new Node(root);
        stack.push(roots);//将初始结点入栈
        //栈不空,加入循环
        while(!stack.isEmpty()){
            Node node = stack.pop();
            if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环

            if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
                //这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
                Node left = new Node(node.treenode.left);
                Node right = new Node(node.treenode.right);

                //改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
                //比如说这里是node,right,left入栈顺序,那么输出结果就是left,right,node,将node看成根结点
                node.flag = true;//修改访问轨迹
                stack.push(node);
                stack.push(right);
                stack.push(left);

            }else{
                list.add(node.treenode.val);
            }
        }
        return list;
    }

代码解释

无论是哪一种遍历,遍历结果左结点一定在右结点的前面,所以在栈中,该层右结点一定要比左结点先入栈;也就是说stack.push(right);要在stack.push(left);前面

运行效果

栈迭代中序

无

标记法中序

无

完整源代码

package com.easy;

import java.util.*;

//Definition for a binary tree node.
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;
    }

}

//因为题目给的TreeNode结构体不包含flag,所以我们给加一个
class Node{
    Boolean flag = false;//表示结点的访问状态,默认false
    TreeNode treenode;

    Node(TreeNode treenode){
        this.treenode = treenode;
    }
    Node(Boolean flag, TreeNode node) {
        this.flag = flag;
        this.treenode = node;
    }
}

public class inorderTraversal {
    public static void main(String[] args) {
        /**
         * 构建二叉树
         *      1
         *   2    3
         *  4 5  6
         */
        TreeNode n4 = new TreeNode(4);
        TreeNode n5 = new TreeNode(5);
        TreeNode n6 = new TreeNode(6);
        TreeNode n2 = new TreeNode(2, n4, n5);
        TreeNode n3 = new TreeNode(3, n6, null);
        TreeNode root = new TreeNode(1, n2, n3);

        //递归
        List<Integer> list1 = orderTraversal_dg(root);

        //栈迭代
//        List<Integer> list = inorderTraversal_stack(root);
//        List<Integer> list = preTraversal_stack(root);
        List<Integer> list2 = postTraversal_stack(root);


        //标记法
//        List<Integer> list = preTraversal(root);
//        List<Integer> list = inorderTraversal(root);
        List<Integer> list3 = postTraversal(root);


        //后序遍历结果
        list1.forEach(item-> System.out.print(item + " "));
        System.out.println();
        list2.forEach(item-> System.out.print(item + " "));
        System.out.println();
        list3.forEach(item-> System.out.print(item + " "));


    }
//-------------------------------------------------------------------------------
//    使用flag标记节点的状态,新节点为false,已访问的节点为true。
//    如果遇到的节点为false,则将其标记为true,然后将其右子节点、自身、左子节点依次入栈。
//    如果遇到的节点为true,则将节点的值输出。
//----此法比递归效率高,相比于栈迭代逊色一些,但好处在于,只要掌握一种,就能像递归一样随意得到另外两种,只需要交换核心代码顺序即可
//----栈迭代法如果只是前序和中序,那还好,如果是后序,那代码改动范围太大,不太友好
    //前序遍历
    public static List<Integer> preTraversal(TreeNode root) {
        Stack<Node> stack = new Stack<>();//定义工具栈
        List<Integer> list = new ArrayList<>();
        Node roots = new Node(root);
        stack.push(roots);//将初始结点入栈
        //栈不空,加入循环
        while(!stack.isEmpty()){
            Node node = stack.pop();
            if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环

            if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
                //这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
                Node left = new Node(node.treenode.left);
                Node right = new Node(node.treenode.right);

                //改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
                //比如说这里是right,left,node入栈顺序,那么输出结果就是node,left,right,将node看成根结点
                stack.push(right);
                stack.push(left);
                node.flag = true;//修改访问轨迹
                stack.push(node);
            }else{
                list.add(node.treenode.val);
            }
        }
        return list;
    }

    /**
     * 标记法,false未访问,true已访问
     * @param root
     * @return
     */
    public static List<Integer> inorderTraversal(TreeNode root) {
        Stack<Node> stack = new Stack<>();//定义工具栈
        List<Integer> list = new ArrayList<>();
        Node roots = new Node(root);
        stack.push(roots);//将初始结点入栈
        //栈不空,加入循环
        while(!stack.isEmpty()){
            Node node = stack.pop();
            if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环

            if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
                //这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
                Node left = new Node(node.treenode.left);
                Node right = new Node(node.treenode.right);

                //改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
                //比如说这里是right,node,left入栈顺序,那么输出结果就是left,node,right,将node看成根结点
                stack.push(right);
                node.flag = true;//修改访问轨迹
                stack.push(node);
                stack.push(left);
            }else{
                list.add(node.treenode.val);
            }
        }
        return list;
    }

    //后序遍历
    public static List<Integer> postTraversal(TreeNode root) {
        Stack<Node> stack = new Stack<>();//定义工具栈
        List<Integer> list = new ArrayList<>();
        Node roots = new Node(root);
        stack.push(roots);//将初始结点入栈
        //栈不空,加入循环
        while(!stack.isEmpty()){
            Node node = stack.pop();
            if (node.treenode == null)continue;//如果结点是空的,那我们就跳过此次循环

            if (!node.flag){//该结点没有访问过,按照右子结点,自己,左子结点顺序入栈,并修改自己的flag,表示已访问
                //这里是因为stack为Node类型,所以我们要把左右结点封装成Node类型,便于入栈
                Node left = new Node(node.treenode.left);
                Node right = new Node(node.treenode.right);

                //改变前,中,后序遍历的关键点,因为引入了栈,所以right、left、node的入栈顺序的反序就是遍历的结果
                //比如说这里是node,right,left入栈顺序,那么输出结果就是left,right,node,将node看成根结点
                node.flag = true;//修改访问轨迹
                stack.push(node);
                stack.push(right);
                stack.push(left);

            }else{
                list.add(node.treenode.val);
            }
        }
        return list;
    }

//-------------------------------------------------------------------------------
    //前序遍历
    public static List<Integer> preTraversal_stack(TreeNode root) {

        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        //当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
        while(root != null || !stack.isEmpty()){
            //只要结点不空,我们就将其及其左结点入栈
            while (root != null){
                list.add(root.val);//将值加入到list中
                stack.push(root);
                root = root.left;
            }
            //入栈结束后,就可以将值加入到list中了
            root = stack.pop();//更新root
            root = root.right;//尝试看root有没有右结点,没有就将root置空,进入下一次循环
        }

        return list;
    }

    /**
     * 这里我们用栈来解决
     * @param root
     * @return
     */
    public static List<Integer> inorderTraversal_stack(TreeNode root) {

        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        //当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
        while(root != null || !stack.isEmpty()){
            //只要结点不空,我们就将其及其左结点入栈
            while (root != null){
                stack.push(root);
                root = root.left;
            }
            //入栈结束后,就可以将值加入到list中了
            root = stack.pop();//更新root
            list.add(root.val);//将值加入到list中
            root = root.right;//尝试看root有没有右结点,没有就将root置空,进入下一次循环
        }

        return list;
    }

    public static List<Integer> postTraversal_stack(TreeNode root) {

        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);//先将根结点入栈
        //当栈不空或者root非空的情况下,我们循环,因为这说明还有元素要处理
        while(!stack.isEmpty()){
            root = stack.pop();//出栈
            list.add(root.val);//将值加入到list中

            //只要结点不空,我们就将其及其左结点入栈
            if (root.left != null) stack.push(root.left);
            if (root.right != null) stack.push(root.right);

        }
        //因为以上述代码得到的list与我们想要的后序遍历输出顺序相反,所以这里进行逆序操作
        Collections.reverse(list);//使用Collections类里头的方法将list反转
        return list;
    }
//-------------------------------------------------------------------------------
    /**
     * 这个前,中,后是针对根节点而言的,递归实现
     *
     * @param root
     * @return
     */
    public static List<Integer> orderTraversal_dg(TreeNode root) {

        List<Integer> list = new ArrayList<>();
//        preorder(root, list);
//        inorder(root, list);
        postorder(root, list);
        return list;
    }

    //前序遍历,PLR
    //中序遍历,LPR
    //后序遍历,LRP
    public static void preorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
        if (root == null) return;

        list.add(root.val);
        preorder(root.left, list);//递归遍历左子树
        preorder(root.right, list);//递归遍历右子树

    }
    /**
     * 递归的核心部分
     * @param root
     * @param list
     */
    public static void inorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
        if (root == null) return;

        //在递归的作用下,程序会一直遍历到二叉树的最底层左叶子结点,然后将其值记录,然后回到上一层递归,记录根结点,再访问右结点
        inorder(root.left, list);//递归遍历左子树
        list.add(root.val);//前序和后续只需要将该语句放到相应位置即可
        inorder(root.right, list);//递归遍历右子树

    }

    public static void postorder(TreeNode root, List<Integer> list) {//这里引入list,用于保存遍历的顺序
        if (root == null) return;

        postorder(root.left, list);//递归遍历左子树
        postorder(root.right, list);//递归遍历右子树
        list.add(root.val);

    }
//-------------------------------------------------------------------------------
}

运行截图

无

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值