二叉树(二) 常见基础操作解析|结点数、树高、查找结点、判断完全二叉树

前言❤️❤️

hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹

这篇博客会解析二叉树几个基础操作,包括求二叉树的结点个数、第k层结点、高度、结点查找、完全二叉树的判断

这些操作大部分都是通过递归的方式来实现的,

这个专栏的数据结构是代码都是用Java来写的,JavaSE专栏现在已经全部更新完成,铁汁们复习基础知识时非常推荐使用,可以试一下💪💪💪

🎇个人主页:洋不写bug的博客
🎇所属专栏:数据结构专栏
🎇复习Java基础知识:Java学习之旅,从入门到进阶
🎇铁汁们对于数据结构基础的各种核心知识(不太常用的也有😆),都可以在上面的数据结构专栏学习,专栏正在持续更新中🐵🐵,有问题可以写在评论区或者私信我哦~

1,基础代码

  • 在写方法之前,首先要把二叉树创建出来,创建一个Node类,表示二叉树的结点,再写个createTree方法,创建一个二叉树,createTree方法返回的是二叉树的根结点,代码如下:
java 复制代码
public class Node {
    public String val;
    public Node left;
    public Node right;

    public Node(String val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }

    @Override
    public String toString() {
        return "Node{" +
                "val='" + val + '\'' +
                ", left=" + left +
                ", right=" + right +
                '}';
    }
}
java 复制代码
public class Test {
    
    public static Node createTree(){
        Node a = new Node("A");
        Node b = new Node("B");
        Node c = new Node("C");
        Node d = new Node("D");
        Node e = new Node("E");
        Node f = new Node("F");
        Node g = new Node("G");
        a.left = b;
        a.right = c;
        b.left = d;
        b.right = e;
        e.left = g;
        c.right = f;
        return a;
    }
    public static void main(String[] args) {
       Node root = createTree();
    }
}

2,求二叉树的结点个数

  • 需要写个size方法,传入二叉树的根结点,就能得出二叉树的结点个数
  • 铁汁们想下,这个是不是跟二叉树的遍历是类似的,只不过每次访问结点的操作改为size++,使用前序、中序、后序都是可以的,这里我们就使用前序,代码如下:
java 复制代码
    public static int size(Node root){
        int size = 0;
        if(root == null){
            return 0;
        }
        size++;
        size(root.left);
        size(root.right);
        return size;
    }
  • 这个代码大概分析下,似乎是没问题的,搞个简单的树结构代入下,size++确实是执行了三次

  • 接下来就在main方法中打印一下,createTree方法里面创建的树有7个结点,预期打印的结果应该是7,但是打印结果却是1

java 复制代码
    public static void main(String[] args) {
        Node root = createTree();
        System.out.println(size(root));
    }
  • 这是因为调用size方法时,每次递归,都会int size = 0,都会创建一个全新的size变量,所以不是在原来的size的基础上++的,因此打印结果就是1
  • 对于这种情况,有两种解决方案,第一种就是使用成员变量(前面size写在方法中属于是局部变量),这样再次运行main方法,打印的就是7
java 复制代码
    private static int size = 0;
    public static int size(Node root) {
        if (root == null) {
            return 0;
        }
        size++;
        size(root.left);
        size(root.right);
        return size;
    }
  • 这种写法有个很大的弊端,就是一次main方法执行只能使用一次,第二次使用结果一定是错的,因为使用完size方法后,size的值不会更新到0
    在main方法中,再传入一个单独的结点,判断长度,size是不是从0开始加的,是在上棵树的基础上加的(使用后传入个单独的结点,结果就会打印8)
java 复制代码
    public static void main(String[] args) {
        Node root = createTree();
        System.out.println(size(root));
        Node root2 = new Node("a");
        System.out.println(size(root2));
    }
  • 使用成员变量这种做法,在做OJ题的时候也是不行的,因为OJ题会有很多判例,除了第一个判例结果是对的,后面的就都是错的
  • 比较推荐的写法就是还是使用递归,通过返回值把当前子树的递归结果返回到上层方法中,上层方法再进行累加
java 复制代码
    public static int size(Node root){
        if(root == null){
            return 0;
        }
        int rootSize = 1;
        int leftSize = size(root.left);
        int rightSize = size(root.right);
        return rootSize + leftSize + rightSize;
    }
  • 这个代码可以这样来理解,一棵树的size由三个部分组成:根结点(结点个数为1)、左子树的结点个数、右子树的结点个数
  • 左子树和右子树的结点个数也按同样的方法来算,也是由子树根结点,子树根结点的左子树结点个数,子树根结点的右子树结点个数组成,就这样递归来计算
  • 画图分析下这个方法的递归过程

  • 在main方法中测试下

java 复制代码
    public static void main(String[] args) {
        Node root = createTree();
        System.out.println(size(root));
        Node root2 = new Node("a");
        System.out.println(size(root2));
    }

3,二叉树第k层结点的个数

  • 写一个getKLevelNodeCount方法,传入二叉树的根结点和k,就能返回该二叉树第k层结点的个数、
  • 当k < 1的时候,就是非法输入,当k = 1的时候,如果树不为空,那结点个数就是1,我们需要分析当k > 1时,结点的个数如何计算
    前面的size方法就是通过递归不断的缩小问题规模,直到问题规模缩小为1,也就是递归到size(叶子结点),这个题目也可以用类似的思想来解决

  • 求二叉树的第k层的结点个数,可以转化为求二叉树左子树的k - 1层的结点个数 + 二叉树右子树的k - 1层的结点个数;接着可以继续缩小问题规模,就比如二叉树左子树的k - 1层的结点个数,等于二叉树的左子树的左子树的k - 1层结点个数,就这样一直递归去缩小问题规模

  • 就比如求这个树的第3层结点个数,就等于B这个子树第2层的结点个数 + C这个子树第2层的结点个数;继续递归缩小规模,B这个子树的第二层结点个数就等于D这个子树的第1层结点个数 + E这个子树第1层的结点个数

  • 代码如下

java 复制代码
    public static int getKLevelNodeCount(Node root,int k){
        if(root == null || k < 1){
            return 0;
        }
        if(k == 1){
            return 1;
        }
        return getKLevelNodeCount(root.left,k - 1) + getKLevelNodeCount(root.right,k  - 1);
    }

4,获取二叉树的高度

  • 二叉树的高度就是从根结点出发到任意结点,路径的最大长度

  • 这个题目依然是是使用递归逐步减小问题规模
    树A的长度 = 1 + MAX(树B的长度,树C的长度)
    树B的长度 = 1 + MAX(树D的长度,树E的长度)

  • 递归过程想明白,代码写起来就特别简单

java 复制代码
    public static int getHeight(Node root){
        if(root == null){
            return 0;
        }
        return 1 + Math.max(getHeight(root.left),getHeight(root.right));
    }
  • 这里博主就不再画递归过程图了,对递归掌握不太好的铁汁,可以自己在画图板上画下递归调用图,就能很好的理解了💪🐵

5,查找树中值为value的结点

  • 这个还是个递归的过程,先判根结点的val是否是value,如果不是,就在左子树中递归来找,如果左子树递归找完了,还没找到,就在右子树中递归来找,如果右子树也递归完没找到,就说明值为value的结点不存在,返回null
java 复制代码
    public static Node find(Node root,String value){
        if(root == null){
            return null;
        }
        if(root.val.equals(value)){
            return root;
        }
        Node leftResult = find(root.left,value);
        if(leftResult != null){
            return leftResult;
        }
        Node rightResult = find(root.right,value);
        if(rightResult != null){
            return rightResult;
        }
        return null;
    }
  • 在右子树中,如果查找不到,就直接返回null了,这里代码写的可以再精简一些
java 复制代码
    public static Node find(Node root,String value){
        if(root == null){
            return null;
        }
        if(root.val.equals(value)){
            return root;
        }
        Node leftResult = find(root.left,value);
        if(leftResult != null){
            return leftResult;
        }
        return find(root.right,value);
    }
  • 画图模拟下递归过程,要查找value为"D"的结点

  • 在Node类中重写个toString方法,就可以在main方法中打印结点的信息,这里就直接alter + insert键,插入自动生成的

java 复制代码
    public String toString() {
        return "Node{" +
                "val='" + val + '\'' +
                ", left=" + left +
                ", right=" + right +
                '}';
    }
  • 在main方法中查找元素值为B的结点,打印这个结点的信息(toString方法中也打印了这个结点left和right结点的信息)

  • java 复制代码
        public static void main(String[] args) {
            Node root = createTree();
            System.out.println(find(root,"B"));
        }

    6,判断完全二叉树

  • 完全二叉树的特点就是在每一层,都是从左往右铺结点的,铺完铺不完无所谓,重要的是中间不能跳过,根据完全二叉树的特征,我们就可以分析出这里应该采用层序遍历来遍历二叉树

  • 单独的层序遍历代码如下,就是通过队列的方式进行层序遍历,写这个方法的前提理解层序遍历代码的原理(上篇博客中有代码的详细解析🐵)

java 复制代码
    public static void levelOrder(Node root) {
        if (root == null) {
            return;
        }
        Queue<Node> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            Node cur = queue.poll();
            System.out.printf(cur.val + " ");
            if (cur.left != null) {
                queue.offer(cur.left);
            }
            if (cur.right != null) {
                queue.offer(cur.right);
            }
        }
    }
  • 接下来就分析下如何判断,层序遍历时是把结点放到队列中去,接着出队列打印,再把这个结点的左结点和右结点加到队列中去
    如下图,B结点在出队列后,队列中下个是C结点,下面这几种情况下,只要C结点有子结点,这个树就一定不是完全二叉树

    1. B结点的左右子树都为空(B.left == null && B.right == null)
    2. B结点的左子树不为空,右子树为空(B.left != null && B.right == null)
    3. B结点的左子树为空,右子树不为空(B.left == null && B.right != null),这种情况也没必要再把C出队列判断了,直接返回false即可
  • 那经过前面的分析,可以设定个第一阶段和第二阶段,在层序遍历时,对于出队列的结点进行判断,分为以下几种情况

    1. 左右子树都为空,进入第二阶段;
    2. 左子树不为空,右子树为空,进入第二阶段
    3. 如果左子树为空,右子树不为空,那就直接返回false即可
    4. 左右子树都不为空,就继续正常的进行层序遍历
  • 下个结点出队列时,判断是第一阶段还是第二阶段

  • 如果为第二阶段,想要满足完全二叉树,那下个结点的左子树和右子树都必须为空,不满足的话,就直接返回false

  • 如果为第一阶段,那就继续按照上面的四种情况进行判断

  • 写代码时注意,空树也属于是完全二叉树,所以如果root为null,就返回true,维护一个boolean类型的变量isFirstStep,来确定当前是第一阶段还是第二阶段,如果满足了进入第二阶段的条件,就把isFirstStep的值改为false
  • 如果层序遍历完都没有返回false,那就返回true
java 复制代码
    public static boolean isComplete(Node root){
      if(root == null){
          return true;
      }
      Queue<Node> queue = new ArrayDeque<>();
      boolean isFirstStep = true;
      queue.offer(root);
      while(!queue.isEmpty()){
          Node cur = queue.poll();
          if(isFirstStep){
              if(cur.left == null && cur.right == null){
                  isFirstStep = false;
              }else if(cur.left != null && cur.right == null){
                  isFirstStep = false;
                  queue.offer(cur.left);
              }else if(cur.left == null && cur.right != null){
                  return false;
              }else{
                  queue.offer(cur.left);
                  queue.offer(cur.right);
              }
          }else{
              if(cur.left != null || cur.right != null){
                  return false;
              }
          }
      }
      return true;
    }
  • 接着修改下createTree方法,创建一个完全二叉树,在main方法中试一下(铁汁们可以自己再创建个非完全二叉树,试一下)
java 复制代码
  public static Node createTree() {
        Node a = new Node("A");
        Node b = new Node("B");
        Node c = new Node("C");
        Node d = new Node("D");
        Node e = new Node("E");
        Node f = new Node("F");
        Node g = new Node("G");
        a.left = b;
        a.right = c;
        b.left = d;
        b.right = e;
        c.left = f;
        c.right = g;
        return a;
    }
java 复制代码
    public static void main(String[] args) {
        Node root = createTree();
        System.out.println(isComplete(root));
    }

结语

  • 递归算是二叉树解题的灵魂,很多复杂问题都可以通过递归逐步缩小问题规模,递归的代码写起来也特别简短,但要分析透递归的过程却不容易,需要多画几次图,细品下递归过程,这样分析递归的能力就会逐渐提升💪💪💪

  • 以上就是今天的所有内容啦~完结撒花~🥳🎉🎉

    z

相关推荐
果果燕1 小时前
实习笔记(六)主机按钮状态上报完整流程
开发语言·c++·php
菠萝加点糖1 小时前
Maven 坐标系统说明
java·maven
化雨成烟1 小时前
C++之AVL树
数据结构
hm宋2 小时前
Canal 升级 jar 包操作文档(以升级 FastJSON 为例)
java·canal·jar
后台模板学习2 小时前
学习的心态高频面试题
java·数据库·学习
平头哥技术团队2 小时前
Day 16 | 用 ul、ol、li 一个标签,把散资料收成有序的技能清单页
开发语言·前端·html·html5
统计学小王子2 小时前
数学建模国赛倒计时 2 天 ——《统计模型精讲(广义线性模型 R 语言实例 + 分析)》
开发语言·数学建模·r语言
用户3126874877202 小时前
线程池到底怎么管理线程的?从 ThreadPoolExecutor 到拒绝策略全链路拆解
java
此冬歌咏2 小时前
自动化服务器运维监控系统(python+shell)
linux·运维·服务器·开发语言·python·自动化