当前位置: 首页 > article >正文

Java-数据结构-二叉树-基础 (o゚▽゚)o

文本目录:

❄️一、树形结构:

   ▶ 1、概念:

▶ 2、特殊的概念:

 ▶ 3、树的表示形式:

❄️二、二叉树:

   ▶ 1、概念:

   ▶ 2、两种特殊的二叉树:

          ➷ 1)、满二叉树:

           ➷ 2)、完全二叉树:

  ▶ 3、二叉树的性质:

       ➷ 1)性质一:

        ➷ 2)性质二:

         ➷ 3)性质三:

 ​编辑

         ➷ 4)性质四:

          ➷ 5)性质五:

   ▶ 4、二叉树的存储:

    ▶ 5、二叉树的基本操作:

        ➷ 1)、二叉树的创建:

 ➷ 2)、二叉树的遍历:

           •(1)、前中后序遍历:

             •(2)、层序遍历:

  ➷ 3)、二叉树的基本操作:

            •(1)、size(Node root):

             •(2)、getLeafNodeCount(Node root):

              •(3)、getKLevelNodeCount(Node root,int k):

               •(4)、getHeight(Node root):

                •(5)、find(Node root,char val):

                 •(6)、isCompleteTree(Node root):

❄️总结:


❄️一、树形结构:

     在我们介绍二叉树之前呢,我们先来了解一下什么是树形结构。

    1、概念:

       树是一种 非线性的数据结构 ,其是由 n 个有限节点组成的一个具有层次关系的集合。其结构呢就像是一个倒挂着的一颗树,所以我们称之为树。

      特点:1、有一个特殊的节点,称为根节点,并且根节点没有前驱节点。

                 2、除了根节点外,其余节点被分为 M 个互不相交的集合,其中呢每一个集合都是一颗 与树相似的子树,每个子树的根节点有且只有一个前驱,其中可以有0个或者多个后继结点。

                 3、 我们的树是递归实现的。

                 4、一颗有N个节点的树,有N - 1 条边。

这里要注意:树形结构中呢,我们的子树之间不能有交集,否则就不是树形结构了。


 2、特殊的概念:

 我们先来看个树形结构的图片: 


☑ 结点的度:一个结点含有子树的个数称之为该结点的度。比如:上图中 A 的度为 6 。

☑ 树的度:一棵树中,所有 结点的度 的最大值称之为 树的度。比如:我们上图的树的度为 6 

☑ 叶子结点或终端结点:度为 0 的结点称之为叶子结点。比如:我们上图的B、C、H、I、K....等

☑ 双亲结点或父结点:若一个结点含有子结点,则这个节点称之为其子结点的父结点。比如:我                                       们上图的 A 是 B 的父结点。

☑ 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点比如:B是A的孩子结                                      点

☑ 根结点:一棵树中没有双亲结点的结点比如:A结点

☑ 结点的层次:从根结点开始定义起,根为第一层1,根的子结点为第2层,以此类推。

☑ 树的高度或者深度:树中结点的最大层次比如:上图中树的高度为 4


接下来的概念呢,我们只需要了解即可:

☒ 非终端结点或分支结点:度不为 0 的结点比如:A、D、E、F、G....等

☒ 兄弟结点:具有相同父结点的结点互相称之为兄弟结点比如:B、C就是兄弟结点

堂兄弟结点:双亲在同一层的结点互为堂兄弟比如:H、I 就是堂兄弟

结点的祖先:从根到该结点所经分支上的所有结点比如:Q 的祖先就是J、E、A

子孙:以结点为根的子树中任意结点都称之为该结点的子孙比如:所有结点都是A的子孙

森林:由m 棵互不相交的树组成的就是森林


  3、树的表示形式:

        树结构呢,相对于线性结构呢就比较复杂了,我们存储表示起来就比较麻烦了。我们对于树有很多的表示形式,比如:双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法。

        我们来了解一下我们最常用的表示方法:孩子兄弟表示法

class Node {
   int val;
   Node firstChild;//第一个孩子引用
   Node nextBrother;//下一个兄弟引用
}

 

 这个呢就是我们的孩子兄弟表示法。


❄️二、二叉树:

   ▶ 1、概念:

       一颗二叉树是结点的一个有限集合,这个集合呢:

1、或者为空

2、或者是由一颗根结点加上两个子树,分别为 左子树 和 右子树 所构成的。

我们来看看二叉树的图片: 

从上面的图片可知:1、二叉树不存在结点大于2的情况。

                                 2、二叉树的子树有左右之分,次序不能颠倒,所以二叉树是有序的。

 注意:我们二叉树可能出现几种情况:


   2、两种特殊的二叉树:

          1)、满二叉树:

         一颗二叉树,如果每层的结点数都达到最大值,则这颗二叉树就是满二叉树。

就是 如果一颗二叉树的结点为 k,并且结点总数为 2^k - 1 个结点,则其就是满二叉树


            2)、完全二叉树:

        完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。

        这样看文字可能不是很看懂,我们来看图片:
完全二叉树的存放顺序为:从上到下,从左到右,依次存放。


  3、二叉树的性质:

       ➷ 1)性质一:

       若规定根结点的层数为1,则一颗 非空二叉树 的第 i 层上最多有 2^(i - 1) 个结点。


        ➷ 2)性质二:

     若规定只有根结点的二叉树的深度为1,则深度为 k 的二叉树的最大结点数为 2^k - 1


         ➷ 3)性质三:

对于任意的一颗二叉树,如果其叶子结点个数为n0,度为2的非叶子结点数为n2,则有n0 = n2 + 1

 


         ➷ 4)性质四:

   具有 n 个结点的 完全二叉树 的 深度k 为 log(n+1) 向上取整,这里的 log是以2为底的

 


          ➷ 5)性质五:

        对于具有n个结点的完全二叉树,如果按照 从上至下从左至右 的顺序对所有节点从0开始编号,则对于序号为 i 的结点有:

          ☞  若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点

          ☞  若2i+1<n,左孩子序号:2i+1,否则无左孩子

          ☞  若2i+2<n,右孩子序号:2i+2,否则无右孩子


   4、二叉树的存储:

   二叉树的存储解构呢,分为:顺序存储链式存储

我们先来看 链式存储:是通过一个一个结点引用起来的,我们常见的有二叉三叉表示形式。

//二叉
class Node {
    int val;//数据域
    Node left;//左孩子引用
    Node right;//右孩子引用
}

//三叉
class Node {
    int val;//数据域
    Node left;//左孩子引用
    Node right;//右孩子引用
    Node parent;//当前结点的根结点
}

    5、二叉树的基本操作:

        ➷ 1)、二叉树的创建:

     我们这里呢只是进行简单的创建,手动的进行创建一个二叉树,之后我们再来写其操作,等到后面我们再来进行二叉树的真正的创建方法。

我们来手动创建这个二叉树,我们使用二叉的方式来进行创建。 

public class BinaryTree {

    static class TreeNode {
        public char val;
        public TreeNode right;
        public TreeNode left;

        public TreeNode(char val) {
            this.val = val;
        }
    }
    public TreeNode createTree() {
        TreeNode A = new TreeNode('A');
        TreeNode B = new TreeNode('B');
        TreeNode C = new TreeNode('C');
        TreeNode D = new TreeNode('D');
        TreeNode E = new TreeNode('E');
        TreeNode F = new TreeNode('F');
        TreeNode G = new TreeNode('G');

        A.left = B;
        A.right = C;
        B.left = D;
        B.right = E;
        C.left = F;
        C.right = G;
        return A;
    }
}

      这样我们就把这个二叉树创建成功了,注意这里的二叉树创建不是真正的二叉树的创建。我们接下来看看二叉树的操作:


 ➷ 2)、二叉树的遍历:

    对于我们的二叉树操作呢,最简单的就是遍历操作了,所谓的遍历:就是沿着某条搜索路线,依次对树中每个结点均做一次访问且只做一次访问

           •(1)、前中后序遍历:

  我们在前中后序遍历中使用递归的方法进行遍历。

前序遍历:先访问根结点,之后访问根的左子树,最后根的右子树。

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

中序遍历:先访问根的左子树,之后访问根结点,最后为根的右子树。 

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

 后序遍历:先访问根的左子树,之后访问根的右子树,最后访问根结点。

//后序遍历
    public void postOrder(TreeNode root) {
        if (root == null) {
            return;
        }

        postOrder(root.left);
        postOrder(root.right);
        System.out.print(root.val + " ");
    }

我们来看这些遍历的运行结果,我们遍历的就是我们上面创建的二叉树。


             •(2)、层序遍历:

     我们除了前中后序遍历呢,我们还有层序遍历,就是先访问第一层的根结点,之后从上到下,从左到右依次访问结点,就是层序遍历。

比如我们创建的这个二叉树的层序遍历为:

A->B->C->D->E->F->G。 

这里我们层序遍历,我们需要使用队列来配合实现,那么它是怎么做到的呢?我们来看看:

 

  来看看代码:

//层序遍历
    public void levelOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while(!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            System.out.print(cur.val + " ");

            if (cur.left != null) {
                queue.offer(cur.left);
            }
            if (cur.right != null) {
                queue.offer(cur.right);
            }
        }
    }

  ➷ 3)、二叉树的基本操作:

            •(1)、size(Node root):

         获得树中结点的个数。

我们有两种方法,我们第一种就是遍历二叉树,当 root 不为空的时候呢,我们的 usedSize 就++ 

第二种方法就是我们的 左子树结点+右子树结点+1,这个方法来求结点的个数。

我们来看第一种方法的代码:

//返回总结点的个数
    public static int usedSize;//我们用于存放结点个数的
    public int size1(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        usedSize++;
        size1(root.left);//遍历左子树
        size1(root.right);//遍历右子树
        
        return usedSize;
    }

    在我们第一种方法中呢,我们的 usedSize 必须在方法的外面定义,因为要是在方法中的话呢,每次我们递归后,我们之前存放的结点的个数就不会保存了,所以我们要定义在外面。


我们来看看第二种方法:

代码:

public int size2(TreeNode root) {
        if (root == null) {
            return 0;
        }

        return size2(root.left) + size2(root.right) + 1;
    }

             •(2)、getLeafNodeCount(Node root):

 获得叶子结点的个数。

     我们这个同样可以使用两种方法来进行获得叶子结点的个数。第一种:使用 leafSize 来计数,当遇到左子树和右子树都为空的结点就是叶子结点

    第二种:左子树的叶子+右子树的叶子。

我们的这个方法和上面的方法是差不多的,所以这里我们直接看代码。

第一种:

//获得叶子结点的个数
    //第一种
    public static int leafSize;
    public int getLeafNodeCount1(TreeNode root) {
        if (root == null) {
            return 0;
        }

        if (root.left == null && root.right == null) {
            leafSize++;
        }
        
        getLeafNodeCount1(root.left);
        getLeafNodeCount1(root.right);
        
        return leafSize;
    }

第二种: 

//第二种
    public int getLeafNodeCount2(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return 1;
        }
        
        return getLeafNodeCount2(root.left) + getLeafNodeCount2(root.right);
    }

              •(3)、getKLevelNodeCount(Node root,int k):

获得第k层的结点的个数。

   我们求第k层的结点数呢,我们求  root的左子树的k-1层的结点 + root的右子树的k-1层的结点

这样求出来的就是我们的第k层结点数。我们来看看思路图: 

我们来看代码的实现: 

//获得第k层的结点的个数。
    public int getKLevelNodeCount(TreeNode root,int k) {
        if (root == null) {
            return 0;
        }
        if (k == 1) {
            return 1;
        }
        
        return getKLevelNodeCount(root.left,k-1) + getKLevelNodeCount(root.right,k-1);
    }

               •(4)、getHeight(Node root):

获得 二叉树的高度。

     我们需要求 左树的高度 和 右子树的高度的最大值 + 1。在这里我们求 左子树的高度 和  右子树的高度。我们也是使用递归的方法。

 代码:

//获取二叉树的高度
    public int getHeight(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = getHeight(root.left);
        int rightHeight = getHeight(root.right);
        
        //谁高谁加一
        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }

                •(5)、find(Node root,char val):

查看二叉树中有没有val这个值的结点。

这个方法呢,就是我们需要遍历一遍二叉树就可以了。

    我们需要先判断我们的根是不是,如果不是,就去左子树中找,如果还没有,就从右子树中找,如果这里面都没有的话,那么就返回null。

//检测值为value的元素是否存在
    public TreeNode find(TreeNode root,char val) {
        if (root == null) {
            return null;
        }
        //先判断根结点
        if (root.val == val) {
            return root;
        }
        //如果不是就去左子树中寻找
        TreeNode leftNode = find(root.left,val);
        if (leftNode != null) {
            //如果我们在左子树中找到的话,我们在上面根的判断就返回了我们和val相等的结点,
            //在这里我们只需要判断是否为空就可以了,不为空,就是val的这个结点,反之则没有
            return leftNode;
        }
        //去右子树中寻找
        TreeNode rightNode = find(root.right,val);
        if (rightNode != null) {
            //我们这里同上
            return rightNode;
        }
        
        return null;
    }

                 •(6)、isCompleteTree(Node root):

判断该二叉树是否是完全二叉树。

     这个方法呢,我们也需要借助队列来实现,这个步骤和我们的层序遍历是差不多的,但是如果我们队列中两个结点中存在 null 的话,那么就不是完全二叉树。因为完全二叉树是从上到下从左到右依次存放,两个相邻的结点中间不可能会存在 null 。我们利用这个性质就可以判断出是否是完全二叉树了。我们来看思路图:

这里我们要注意的是,当我们的root为空的时候,也是完全二叉树。

我们来看代码:

//判断一棵树是不是完全二叉树
    public boolean isCompleteTree(TreeNode root) {
        if (root == null) {
            return true;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while(!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            if (cur != null) {
                queue.offer(cur.left);
                queue.offer(cur.right);
            }else {
                break;
            }
        }
        
        while(!queue.isEmpty()) {
            TreeNode peek = queue.peek();
            if (peek != null) {
                return false;
            }
            queue.poll();
        }
        
        return true;
    }

❄️总结:

   OK,到这里呢,我们的二叉树基础就到这里就结束了,我们接下来看看关于二叉树的相关的习题,并且在题中我们会看见我们 二叉树的创建方法。让我们期待下次的博客吧!!!拜拜啦~~~


http://www.kler.cn/news/307595.html

相关文章:

  • 代码随想录训练营Day3 | 链表理论基础 | 203.移除链表元素 | 707.设计链表 | 206.反转链表
  • Flink学习2
  • 力扣每日一题
  • 深入剖析:C++类对象的内存布局与优化
  • 【计算机网络】应用层序列化
  • 【每日一题】LeetCode 2398.预算内的最多机器人数目(滑动窗口、数组、二分查找、前缀和、堆(优先队列))
  • 多层建筑能源参数化模型和城市冠层模型的区别
  • Typora2024最新版破解方法(亲测可用)
  • CentOS配置python版本管理工具pyenv
  • Maven 常见问题以及常用命令
  • 函数题 6-2 多项式求值【PAT】
  • UVA-225 黄金图形 题解答案代码 算法竞赛入门经典第二版
  • 电脑浏览器访问华为路由器报错,无法访问路由器web界面:ERR_SSL_VERSION_OR_CIPHER_MISMATCH 最简单的解决办法!
  • 代码随想录打卡Day32
  • Unity 使用Spine动画切换时有残影
  • VSCode创建C++项目和编译多文件
  • java发邮件内容含表格实现方法?如何配置?
  • sqlgun新闻管理系统
  • 本地部署大语言模型详细操作步骤
  • Y电容(安规电容)的作用是什么?
  • 【C++】queue和priority_queue
  • Linux:进程(一)
  • 无人机建模详解!!!
  • [Leetcode LCR 154][Medium]-复杂链表的复制-链表
  • JSON数组
  • 通信工程学习:什么是接入网(AN)中的CF核心功能
  • dplyr、tidyverse和ggplot2初探
  • 一些学习three的小记录
  • RK3588九鼎创展方案在Arm集群服务器的项目中的应用分析​​
  • 关于决策树集成的一份介绍