《数据结构》二叉树(基础题)

👑作者主页:Java冰激凌
📖专栏链接:数据结构 

目录

 绘制二叉树

提出一个新的问题

实现一颗二叉树

节点类

前序遍历

中序遍历

后序遍历

求节点个数

求叶子节点个数

 求第K层节点的个数

获取二叉树的高度

寻找val所在节点 没有找到返回null

检查两棵树是否相同力扣

另一棵树的子树力扣

二叉树的最大深度

判断一棵二叉树是否是平衡二叉树

对称二叉树


 绘制二叉树

 我们经常会遇到这一类的题型  给定二叉树前序遍历(上一章写的是先序遍历 意思是一样的)和中序遍历或者给定二叉树中序遍历和后序遍历来绘制一颗唯一的二叉树 遇到这一类题型我们应该怎么做呢?

我们以上图的题为例 我们首先尝试来做一下 先来感受一下做题过程

 

寻找过程思路分析

        1.首先遇到一道这样的题之前 我们要先来思考一下 我们大概的思路是先找根节点才可以确定到左右子树 所以我们要先从寻找根节点来下手 我们可知的为 前序遍历遍历的顺序为根->左子树->右子树 中序遍历遍历的顺序为 左子树->根 ->右子树 后序遍历遍历顺序为 左子树-> 右子树->根

         2.所以 我们可以根据上述的来寻找根 根据遍历顺序我们可以得到 前序遍历得到第一个必为根 后序遍历最后一个必为根 所以我们可以根据这个定理 根据跟来寻找节点

        3.首先我们取出第一个根 然后在中序遍历中寻找到根所在位置 这个根所在位置的左边 则是根的左子树的元素 右边则是右子树 因为我们刚刚的得到的 中序遍历的遍历顺序是左子树 ->根 ->右子树 

        4.重复1 2 3过程 即可完成一颗二叉树的绘制 看完此处内容再去看上面的Gif是不是看明白了

最后我们得到结果为

 大概总结为下:

        1.首先正在前序遍历/后序遍历中寻找根节点的位置

        2.根的左边是左子树 根的右边是右子树

        3.根据前序/后序 和中序遍历构建出的二叉树是唯一二叉树

提出一个新的问题

如果给一个前序遍历和后序遍历 是否可以创建一颗唯一二叉树?
        答:不能 前序遍历和后序遍历都是用来协助确认根节点的


实现一颗二叉树

        有了第一篇文章的铺垫 以及上述的铺垫 我们已经可以来实现一下二叉树的基本功能

节点类

我们要先实现一个TreeNode类 这个类代表二叉树中的节点

class TreeNode {
    //节点值域
    public int val;
    //左子树地址
    public TreeNode left;
    //右子树地址
    public TreeNode right;
    
    //构造方法
    public TreeNode(int val){
        this.val=val;
    }
}

前序遍历

// 前序遍历
    void preOrderTraversal(TreeNode root){
        if(root==null){
            return ;
        }
        System.out.print(root.val+" ");
        preOrderTraversal(root.left);
        preOrderTraversal(root.right);
    }

中序遍历

    // 中序遍历
    void inOrderTraversal(TreeNode root){
        if(root==null){
            return ;
        }
        inOrderTraversal(root.left);
        System.out.print(root.val+" ");
        inOrderTraversal(root.right);
    }

后序遍历

    // 后序遍历
    void postOrderTraversal(TreeNode root){
        if(root==null){
            return ;
        }
        postOrderTraversal(root.left);
        postOrderTraversal(root.right);
        System.out.print(root.val+" ");
    }

求节点个数

  方法1 :遍历思路 注意!!!这个思路是有很严重的bug的 在遍历二叉树第一次的时候的确是没有问题的 但是如果进行两次以及两次以上就会出现错误 因为是使用静态类变量size记录的 在遍历一次后这个值并不会被清0 所以第二次遍历会继续累计上去 导致错误

时间复杂度O(N) 空间复杂度O(logN)

    // 遍历思路-求结点个数
    static int size = 0;
    void getSize1(TreeNode root){
        if(root==null){
            return ;
        }
        size++;
        getLeafSize1(root.left);
        getLeafSize1(root.right);
    }

 思路2:子问题思路 此代码的思路是直接找到叶子节点 看左右子树返回的节点个数并且返回加自己本身节点 这个+1就是这么来的

时间复杂度O(N) 空间复杂度O(logN)

// 子问题思路-求结点个数
    int getSize2(TreeNode root){
        if(root==null){
            return 0;
        }
        return getSize2(root.left)+getSize2(root.right)+1;
    }

求叶子节点个数

思路1 :遍历思路 也是会存在跟求节点个数思路1同样的问题

时间复杂度O(N) 空间复杂度O(logN)

    // 遍历思路-求叶子结点个数
    static int leafSize = 0;
    void getLeafSize1(TreeNode root){
        if(root==null){
            return ;
        }
        if(root.left==null&&root.right==null){
            leafSize++;
        }
        if(root.left!=null){
            getLeafSize1(root.left);
        }
        if(root.right!=null){
            getLeafSize1(root.right);
        }
    }

思路2: 子问题思路

时间复杂度O(N) 空间复杂度O(logN)

// 子问题思路-求叶子结点个数
    int getLeafSize2(TreeNode root){
        if(root==null){
            return 0;
        }
        if(root.left==null&&root.right==null){
            return 1;
        }
        return getLeafSize2(root.left)+getLeafSize2(root.right);
    }

 求第K层节点的个数

子问题思路 我们要先找到第K-1层 判断K-1层的左右子树是否存在即可

时间复杂度O(N) 空间复杂度O(logN)

 // 子问题思路-求第 k 层结点个数
    int getKLevelSize(TreeNode root,int k){
        if(k<0||root==null){
            return 0;
        }
        if(k-1 == 1){
            int size=0;
            if(root.left!=null){
                size++;
            }
            if(root.right!=null){
                size++;
            }
            return size;
        }
        return getKLevelSize(root.left,k-1)+getKLevelSize(root.right,k-1);
    }

获取二叉树的高度

子问题思路 判断左右子树高的一侧返回并且+1

时间复杂度O(N) 空间复杂度O(logN)

// 获取二叉树的高度
    int getHeight (TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftn = getHeight(root.left);
        int rightn = getHeight(root.right);
        return leftn > rightn ? leftn + 1 : rightn + 1;
    }

寻找val所在节点 没有找到返回null

按照根 左子树 右子树的顺序去寻找 一旦找到直接返回 无需再继续寻找

时间复杂度O(N) 空间复杂度O(logN)

TreeNode find(TreeNode root, int val){
        if(root==null) {
            return null;
        }
        if(root.val==val) {
            return root;
        }
        TreeNode left = find(root.left,val);
        if(left!=null){
            return left;
        }
        TreeNode right = find(root.right,val);
        if(right!=null){
            return right;
        }
        return null;
    }

检查两棵树是否相同力扣

 深度优先搜索思路如果两个二叉树都为空,则两个二叉树相同。如果两个二叉树中有且只有一个为空,则两个二叉树一定不相同 如果两个二叉树都不为空,那么首先判断它们的根节点的值是否相同,若不相同则两个二叉树一定不同,若相同,再分别判断两个二叉树的左子树是否相同以及右子树是否相同。

时间复杂度O(min(m,n)) 空间复杂度O(min(m,n))

 public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p==null&&q==null){
            return true;
        }
        if(p==null||q==null){
            return false;
        }
        if(p.val!=q.val){
            return false;
        }
        return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
    }

另一棵树的子树力扣

深度优先搜索枚举  当找到节点值与子树根节点值相同时进行判断 

时间复杂度O(S*T) 空间复杂度O(max(S,T);

    public boolean subIsSubTree(TreeNode root,TreeNode subRoot){
        if(root==null&&subRoot==null){
            return true;
        }
        if(root==null||subRoot==null){
            return false;
        }
        if(root.val!=subRoot.val){
            return false;
        }
        return subIsSubTree(root.left,subRoot.left)&&subIsSubTree(root.right,subRoot.right);
    }
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root==null&&subRoot==null){
            return true;
        }
        if(root==null||subRoot==null){
            return false;
        }
        if(root.val==subRoot.val){
            if(subIsSubTree(root,subRoot)){
                return true;
            }
        }
        return isSubtree(root.left,subRoot)||isSubtree(root.right,subRoot);
    }

二叉树的最大深度

其实这个题可转变一下思路便成为了二叉树的高度

时间复杂度O(N) 空间复杂度O(logN)

    public int maxDepth(TreeNode root) {
        if(root==null){ 
            return 0;
        }
        int leftcount=maxDepth(root.left);
        int rightcount=maxDepth(root.right);
        return leftcount>rightcount?leftcount+1:rightcount+1;
    }

判断一棵二叉树是否是平衡二叉树

深度优先搜索暴力 非常不推荐的写法 时间复杂度较高 从根节点开始判断是否是平衡二叉树 自下而上

时间复杂度O(N^2) 空间复杂度O(N^N)

public int size(TreeNode root){
        if(root==null){
            return 0;
        }
        int leftn=size(root.left);
        int rightn=size(root.right);
        return leftn>rightn?leftn+1:rightn+1;
    }
    public boolean isBalanced(TreeNode root) {
        if(root==null){
            return true;
        }
        boolean a=isBalanced(root.left);
        if(Math.abs(size(root.left)-size(root.right))>1){
            return false;
        }
        boolean b=isBalanced(root.right);
        if(Math.abs(size(root.left)-size(root.right))>1){
            return false;
        }
        return a&&b;
    }

 自底向上的递归 

由于是自顶向下递归,因此对于同一个节点,函数 height 会被重复调用,导致时间复杂度较高。如果使用自底向上的做法,则对于每个节点,函数height 只会被调用一次。

时间复杂度O(N) 空间复杂度O(N)

public boolean isBalanced(TreeNode root) {
        return height(root) >= 0;
    }

    public int height(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);
        if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {
            return -1;
        } else {
            return Math.max(leftHeight, rightHeight) + 1;
        }
    }

对称二叉树

如果一个树的左子树与右子树镜像对称,那么这个树是对称的。

我们可以设定两个引用 分别判断左子树的左和右子树的右与左子树的右和右子树的左

public boolean _isSymmetric(TreeNode left,TreeNode right){
        if(left==null&&right==null){
            return true;
        }
        if(left==null||right==null){
            return false;
        }
        if(left.val!=right.val){
            return false;
        }
        return _isSymmetric(left.left,right.right) && _isSymmetric(left.right,right.left);
    }
    public boolean isSymmetric(TreeNode root) {
        if(root==null){
            return true;
        }
        return _isSymmetric(root.left,root.right);
    }

未完待续~ 

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java冰激凌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值