【Java】代码随想录二叉树01 | 前序、中序和后序的递归遍历和迭代遍历

目录

二叉树01

1.1 理论基础

1.1.1 二叉树的类型

1.1.2 二叉树的存储方式

1.1.3 二叉树的遍历方式

1.2 递归遍历

1.3 迭代遍历


二叉树01

1.1 理论基础

1.1.1 二叉树的类型

满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。

完全二叉树的定义如下:除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。

二叉搜索树:有数值的有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。

C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树。

1.1.2 二叉树的存储方式

链式存储方式就用指针, 顺序存储的方式就是用数组。顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起。

用数组来存储二叉树如何遍历的呢?如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。

1.1.3 二叉树的遍历方式

  • 深度优先遍历:先往深走,遇到叶子节点再往回走。
    • 前序遍历(递归法,迭代法)
    • 中序遍历(递归法,迭代法)
    • 后序遍历(递归法,迭代法)

  • 广度优先遍历:一层一层的去遍历。
    • 层次遍历(迭代法)

深度优先遍历:递归、栈

广度优先遍历:迭代、队列

说到二叉树,就不得不说递归,很多同学对递归都是又熟悉又陌生,递归的代码一般很简短,但每次都是一看就会,一写就废。

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;
    }
}

1.2 递归遍历

本篇将介绍前后中序的递归写法,一些同学可能会感觉很简单,其实不然,我们要通过简单题目把方法论确定下来,有了方法论,后面才能应付复杂的递归。

递归三要素:

①参数和返回值        ②终止条件        ③单层递归的逻辑

LC144:二叉树的前序遍历

题目链接:前序遍历

先审题,题干:给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

①参数就是根节点,返回值为整数数组

②终止条件:当前遍历的节点为null

③单层递归逻辑(前序遍历):中左右

所以代码:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();   //返回前序遍历
        preorder(root,result);  //前序遍历,无返回值
        return result;
    }
    public void preorder(TreeNode root,List<Integer> result){
        if(root == null){
            return;
        }
        //前序遍历:中左右
        result.add(root.val);
        preorder(root.left,result);
        preorder(root.right,result);
    }
}

LC145:二叉树的后序遍历

同理

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();   //返回后序遍历
        postorder(root,result);  //后序遍历,无返回值
        return result;
    }
    public void postorder(TreeNode root,List<Integer> result){
        if(root == null){
            return;
        }
        //后序遍历:左右中
        postorder(root.left,result);
        postorder(root.right,result);
        result.add(root.val);
    }
}

LC94:二叉树的中序遍历

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();   //返回后序遍历
        inorder(root,result);  //后序遍历,无返回值
        return result;
    }
    public void inorder(TreeNode root,List<Integer> result){
        if(root == null){
            return;
        }
        //后序遍历:左右中
        inorder(root.left,result);
        result.add(root.val);
        inorder(root.right,result);
    }
}

1.3 迭代遍历

递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。本题也是通过栈来实现迭代。

LC144:二叉树的前序遍历

前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。为什么要先加入右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码。

// 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){    //判断root是否为空
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();    //Stack存储的是节点TreeNode
        stack.push(root);    //新建并初始化Stack
        while (!stack.isEmpty()){    //循环开启迭代
            TreeNode node = stack.pop();
            result.add(node.val);    //中
            if (node.right != null){
                stack.push(node.right);    //右
            }
            if (node.left != null){
                stack.push(node.left);    //左
            }
        }
        return result;
    }
}

LC94:二叉树的中序遍历

前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,则用来处理节点上的元素(将元素放入result数组中)。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();  
        if(root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while(cur != null || !stack.isEmpty()){
            if(cur!=null){  //没到底端时只管推进
                stack.push(cur);
                cur = cur.left; //访问左节点
            }else{  //到了底端时指针返回上一级(中),再访问右节点
                cur = stack.pop();
                result.add(cur.val);
                cur = cur.right;
            }
        }
        return result;
    }
}

 后序遍历

// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);    //相当于中左右(入栈),中右左(出栈)
        while (!stack.isEmpty()){
            TreeNode node = stack.pop();
            result.add(node.val);
            if (node.left != null){
                stack.push(node.left);
            }
            if (node.right != null){
                stack.push(node.right);
            }
        }
        Collections.reverse(result);    //然后翻转
        return result;
    }
}

1.4 统一迭代

我们发现迭代法实现的先中后序,其实风格也不是那么统一,除了先序和后序,有关联,中序完全就是另一个风格了,一会用栈遍历,一会又用指针来遍历。其实针对三种遍历方式,使用迭代法是可以写出统一风格的代码!

统一风格的迭代法并不好理解,而且想在面试直接写出来还有难度的。所以大家根据自己的个人喜好,对于二叉树的前中后序遍历,选择一种自己容易理解的递归和迭代法。

这里省略

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值