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

二叉树的基本概念和底层实现

1. 树型结构

        1.1 认识树

        在学习二叉树之前我们需要了解一下树型结构

树是一种非线性的数据结构,它是由n个结点组成的一个有层次关系的集合,看起来像个倒挂的树,也就是根朝上,枝叶朝下.

        特点:

1. 根结点没有前驱结点

2. 除了根结点外其他的结点被分为互不相交的集合,每个集合又是一棵类似的子树.每棵子树的根结点都只有一个前驱,有0或多个后继.

3. 树是递归定义的

        1.2 树的概念

重点:

结点的度: 一个结点含有子树的个数为结点的度.

树的度: 所有结点的最大的度就是树的度.

叶子结点(终端结点): 度为0的结点.

双亲结点(父节点): 如果一个结点含有子节点,那么这个结点就是其子节点的父节点.

孩子结点(子节点): 一个结点的子树所含有的根结点称为该结点的子节点.

根结点: 一棵树没有双亲的结点.

结点的层次: 从根开始定义,根为第一层,根的子节点为第二层...

树的深度: 指的是从根结点(度为0)自顶向下逐层累加至该结点时的深度.树的深度十数中最大结点的深度.

树的高度: 结点的高度指从该结点最底层的叶子节(度为0)点出发,自定向上逐层累加至该结点的高度.树的高度是树中高度最大的结点的高度.

 

以下了解即可

 非终端结点(分支结点): 度不为0的结点.

兄弟结点: 有相同父亲的结点互称为兄弟结点.

堂兄弟结点: 双亲在同一层的结点互为堂兄弟结点.

结点的祖先: 从根到该结点所经分支上的所有结点.

子孙: 某结点经由下面的结点就是该结点的子孙.

森林: 由m棵互补相交的树组成的集合为森林.

1.3 树的表示形式

        树的存储方法有很多表示方式: 双亲表示法,孩子表示法,孩子双亲表示法,孩子兄弟表示法

主要了解孩子兄弟表示法:

然后树型结构的话主要就应用在我们的文件和目录管理上

2. 二叉树

        2.1 概念

二叉树: 每个结点的度都是<=2

如果一棵树是二叉树,那么它的每一棵子树都是二叉树,二叉树是有序树

        

合成二叉树的结构 

        2.2 俩种特殊的二叉树

满二叉树: 一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。也就是说,如果一棵二叉树的层数为K,且结点总数是2^(k -1) ,则它就是满二叉树。
简而言之就是每层结点都达到最大值:2^(k - 1);


完全二叉树: 全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树
简而言之就是从上到下从左到右依次存放,不能有割开的
满二叉树是一种特殊的完全二叉树

         2.3 二叉树的性质

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

2.  若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 2^k -1(k>=0).

3.  对任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1

4. 具有n个结点的完全二叉树的深度k为 log2(n-1)上取整,2在这里为底数

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

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

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

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

2.3.1 一些相关计算题

第一题:

第二题和第三题:

第四题:

2.4 二叉树的遍历

遍历指的是 沿着某条路线遍历

前序遍历(先根遍历): 根 -> 左子树 -> 右子树

中序遍历: 左子树 -> 根 -> 右子树

后序遍历: 左子树 -> 右子树 -> 根

层次遍历: 从左到右从上到下依次遍历

2.4.1 一些相关的题目

题目一:

题目二: 

 题目三:

题目四

题目五:

 问:如果给你一个前序和后序能否创建一个二叉树?

前序和后序不能创建一个二叉树: 因为前序和后序确定的都是根,不能确定左右.

2.5 二叉树的底层实现

        2.5.1 前置准备

我们来自己实现一个二叉树,为了更好的理解二叉树,我们先手动连接一个二叉树.在这之前我先介绍一些我们自定义的属性和类.首先我们先创建结点内部类,我们使用孩子表示法,每个结点分为val域,left域,right域.然后我们提供一个构造方法,每次创建结点的时候我们都要把val的参数传进去.

然后我们正式进入二叉树的手动连接,我们如图手动构造二叉树,我们只要对着图操纵每个结点的right和left即可.

        2.5.2 实现二叉树的三种遍历操作

前序遍历,我们按照: 根结点 -> 左子树 -> 右子树. 这种顺序来进行遍历,因此我们先要判断根结点是不是空的,然后我们再打印根结点,再递归左子树,然后递归右子树.如图,我们就展示了遍历A的左子树一部分的遍历流程.

        

中序遍历,我们按照: 左子树 -> 根结点 -> 右子树.这种顺序来进行遍历,首先我们还是要判断根结点是不是空的,然后我们先递归左子树,然后递归到最左边结点的时候,我们就打印我们的子树的根结点,然后我们再递归我们的右子树.如图简要分析了一下它的过程.

后序遍历,我们按照: 左子树 -> 右子树 -> 根结点.这种顺序来对二叉树进行遍历.我们先递归左子树,然后递归右子树,最后在进行打印,如图.

2.5.3 获取树中结点的个数

1> 遍历的方式 : 只要遍历的结点不为空,我们就让全局变量++

我们获取树中的结点数目,我们只要遍历整个树,然后经过一个结点就记录一下就可以; ,因此我们选择前序遍历.每次遍历一个结点,我们就让全局遍历++

2> 用子问题的思路来解决: 整棵树的结点数 = 左子树的结点数 + 右子树的结点数 + 根结点(1)

2.5.4 获取叶子结点的个数

1> 遍历的方式: 按照某种方式来进行遍历,遍历到某个结点,判断是不是叶子,是就计数器++

2> 用子问题的思路来写: 整棵数的叶子 = 左子树的叶子 + 右子树的叶子

用子问题,我们就可以充分利用我们的返回值了

2.5.5 获取第k层结点的个数

子问题方法: 第k层节点数 = 左子树的k-1 层的节点数 + 右子树k-1层的结点数

我们从上到下标号,每次遍历到下一层,我们k就--,直到k == 1的时候,我们就遍历到了给定的第k层,然后我们就返回1.

2.5.6 获取二叉树的高度

子问题解决: 整棵树的高度 = max(左子树的高度,右子树的高度) + 1

不定义结点数变量也可以,但是在刷题软件往往会超时,因为第一种写法只递归了一次,而下面这种写法在判断的时候递归了一次,在返回值的时候又递归了一次.

2.5.7 返回检测值为value的结点

我们直接前序遍历,先判断根结点是不是,然后判断左子树里面有没有val,再判断右子树有没有.我们如果没有找到,一定返回的是null,如果找到了,我们一定返回的是那个结点.

2.5.8 整体代码

package 二叉树;import java.util.ArrayList;
import java.util.List;//TODO 孩子表示法
//
public class BinaryTree {static class TreeNode {//每一棵树的结点public char val;public TreeNode left;public TreeNode right;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');TreeNode H = new TreeNode('H');A.left = B;B.left = D;B.right = E;E.right = H;A.right = C;C.left = F;C.right = G;return A;}//进行遍历操作// 前序遍历(根左右)//用递归 的方式来写 : 把大问题处理成小问题,处理方式是一样的void preOrder(TreeNode root) {//先判断根结点为不为空if (root == null) {return;}//不为空就按照根左右的方式来打印;System.out.print(root.val + " ");//遍历根结点的左子树preOrder(root.left);//遍历根结点的右子树preOrder(root.right);}// 中序遍历(左根右)void inOrder(TreeNode root) {if (root == null) {return;}//从左子树开始遍历inOrder(root.left);//打印左子树的值System.out.print(root.val + " ");inOrder(root.right);}// 后序遍历(左右根)void postOrder(TreeNode root) {if (root == null) {return;}postOrder(root.left);postOrder(root.right);System.out.print(root.val+" ");}//把前序遍历的结构存储到List里面去List<Integer> list1 = new ArrayList<>();public List<Integer> preorderTraversal(TreeNode root) {//先判断根结点为不为空if (root == null) {return list1;}//不为空就按照根左右的方式来打印;list1.add((int) root.val);//遍历根结点的左子树preorderTraversal(root.left);//遍历根结点的右子树preorderTraversal(root.right);return list1;//如何合理利用返回值?}//我们进行优化public List<Integer> preorderTraversal2(TreeNode root) {List<Integer> list = new ArrayList<>();//先判断根结点为不为空if (root == null) {return list;}//不为空就按照根左右的方式来打印;list.add((int) root.val);//遍历根结点的左子树List<Integer> leftTree =  preorderTraversal2(root.left);//把左边的树放进去list.addAll(leftTree);//遍历根结点的右子树List<Integer> rightTree = preorderTraversal2(root.right);//把右边的树放进去list.addAll(rightTree);return list;}public int nodeSize;//TODO 获取树中的个数//只要遍历的结点不为空,我们就++//遍历思路来写int size(TreeNode root) {if(root == null) {return 0;}nodeSize++;size(root.left);size(root.right);return nodeSize;//利用返回值来进行优化}//用子问题的思路来解决:整棵树的结点个数 = 左子树结点个数 + 右子树结点个数int size2(TreeNode root) {if(root == null) {return 0;}return size2(root.left) + size2(root.right) + 1;//左子树的结点数 + 右子树的结点数 + 根结点(1)}//TODO 获取叶子结点的个数//遍历思路来写// 按照某种方式来进行遍历,遍历到某个结点,判断是不是叶子,是就计数器++int leafCount;int getLeafNodeCount(TreeNode root){if(root == null) {return 0;}//如果每次遍历我们的结点左右都为空就让全局变量leftCount++;if (root.left == null && root.right == null) {leafCount++;}getLeafNodeCount(root.left);getLeafNodeCount(root.right);return leafCount;}//用子问题的思路来写: 整棵数的叶子 = 左子树的叶子 + 右子树的叶子int getLeafNodeCount1(TreeNode root) {if(root == null) {return 0;}//如果左右都为null那么就返回1if(root.right == null && root.left == null) {return 1;}//计算左子树和右子树的return getLeafNodeCount1(root.left) + getLeafNodeCount1(root.right);}//接下来我们都用子问题来解决//TODO 获取第k层结点的个数//第k层的结点数 = 左子树k-1层的节点数 + 右子树k-1层的节点数int getLeveNodeCount(TreeNode root, int k) {if(root == null) {return 0;}if(k == 1) {return 1;}//计算k-1层的左子树结点 + 计算k-1层的右子树结点return getLeveNodeCount(root.left,k - 1) + getLeveNodeCount(root.right,k - 1);}//TODO 获取二叉树的高度 时间复杂度O(N)因为遍历了每一个结点//整棵树的高度 = max(左子树的高度,右子树的高度) +   1int getHight(TreeNode root) {if (root == null){return 0;}//计算左树的高度int leftHight = getHight(root.left);int rightHight = getHight(root.right);//返回左树和右树的高度最高的+1return leftHight > rightHight ? leftHight + 1 : rightHight + 1;}int getHight2(TreeNode root) {if (root == null){return 0;}//返回左树和右树的最大值+1return getHight(root.left) > getHight(root.right) ? getHight(root.left) + 1 : getHight(root.right) + 1;//这里递归太多次了没有第一种好,第一种只递归了一次}//TODO 检测值为value的元素是否存在//直接前序遍历,先判断根是不是,然后判断左子树是不是,最后判断右子树是不是TreeNode find(TreeNode root ,char val) {if(root == null) {return null;}//判断根是不是if(root.val == val) {return root;}//判断左子树TreeNode ret1 = find(root.left,val);if(ret1 != null) {//如果不为空就找到了return ret1;}//判断右子树是不是TreeNode ret2 = find(root.right,val);if(ret2 != null) {return ret2;}//左右都没找到return null;}}

运行结果:

相关文章:

二叉树的基本概念和底层实现

1. 树型结构 1.1 认识树 在学习二叉树之前我们需要了解一下树型结构 树是一种非线性的数据结构,它是由n个结点组成的一个有层次关系的集合,看起来像个倒挂的树,也就是根朝上,枝叶朝下. 特点: 1. 根结点没有前驱结点 2. 除了根结点外其他的结点被分为互不相交的集合,每个集合又…...

GIF图片格式详解(三)

gif历史部分介绍请参考上一篇《GIF图片格式详解&#xff08;一&#xff09;》&#xff0c; 格式部分详解参考 《GIF图片格式详解&#xff08;二&#xff09;》 或直接访问博客地址&#xff1a;https://blog.whatsroot.xyz/2023/12/16/all-about-gif/ 本篇介绍下用于处理gif图…...

类和对象相关题

文章目录 1. 求123...n2. 计算是这一年的第几天3. 求两个日期之间的天数4. 算出第n天是几月几号5. 计算一个日期加上若干天后是什么日期 1. 求123…n 求123…n&#xff0c;要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句&#xff08;A?B:C&a…...

Word大珩助手:超大数字怎么读?35位数字?69位数字?

俄罗斯日前对谷歌开出了20000000000000000000000000000000000&#xff08;35位数字&#xff09;美元的罚款 这一数字远超全球GDP总和&#xff0c;消息一出很快就登上热搜。 面对这样一个庞大的数字&#xff0c;人们不禁好奇&#xff0c;这样的数字该如何读出来&#xff1f; …...

阿里云k8s-master部署CNI网络插件遇到的问题

问题 按照网络上的部署方法 cd /opt/k8s # 下载 calico-kube-controllers配置文件&#xff0c;可能会网络超时 curl https://docs.projectcalico.org/manifests/calico.yaml -O kubectl apply -f calico.yaml 试了很多次都不行&#xff0c;k8s-master都是Not ready的状态 ca…...

【LwIP源码学习4】主线程tcpip_thread

前言 本文对lwip的主要线程tcpip_thread进行分析。 正文 tcpip_thread是lwip最主要的线程&#xff0c;其创建在tcpip_init函数中 sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);tcpip_init函数被TCPIP_Init函数调用。…...

求猫用宠物空气净化器推荐,有没有吸毛强、噪音小的产品

自从成为铲屎官&#xff0c;真的和当妈没有区别了。家里的毛孩子成天掉毛&#xff0c;我就跟在它屁股后面默默收拾&#xff0c;一举一动都要时刻关注。最近换季&#xff0c;家里还多了不少浮毛&#xff0c;全飘在空气中&#xff0c;阳光照射下非常明显。 我妈看到后各种吐槽&a…...

pycharm中python控制台出现CommandNotFoundError: No command ‘conda run‘.

1、错误现象 pycharm中打开python控制台出现CommandNotFoundError: No command conda run.的错误。 2、背景 conda是4.6版本&#xff0c;在Anaconda Prompt可以正常运行虚拟环境。 3、解决方法 更新conda版本&#xff0c;基本命令&#xff0c;会自动更新到最新版本。 con…...

架构师备考-架构基本概念

目录 基本概念 架构设计与生命周期 需求分析 设计阶段 实现阶段 构件组装阶段 部署阶段 后开发阶段 动态软件体系结构 体系结构恢复与重建 软件架构设计的重要性 基本概念 软件架构&#xff08;Software Architecture&#xff09;设计主要关注软件构件的结构、属性和…...

信奥赛C++知识点

参加信息学奥林匹克竞赛&#xff08;信奥赛&#xff09;所需学习的C知识点&#xff0c;以下是一个详细的知识点列表&#xff1a; 一、C语言基础 程序结构 头文件&#xff1a;包含必要的头文件&#xff0c;如<iostream>用于输入输出。 命名空间&#xff1a;使用using …...

高并发内存池扩展 -- 处理大内存,优化释放时需要传入空间大小,加入定长内存池,存放映射关系的容器的锁机制,优化性能(基数树,优势,优化前后对比)

目录 高并发内存池 扩展 测试 大内存 介绍 代码 优化释放时需要传入空间大小 介绍 赋值 代码 加入定长内存池 引入 介绍 代码 存放映射关系的容器 锁机制 写入 读取 优化性能 引入 基数树 单级基数树 两级基数树 三级基数树 优势 引入代码 优化前后…...

Composite(组合)

1)意图 将对象组合成树型结构以表示“部分-整体”的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。 2)结构 组合模式的结构如图 7-33 所示。 其中: Component 为组合中的对象声明接口;在适当情况下实现所有类共有接口的默认行为;声明一个接口用于访问…...

有Bootloader,为什么还要BROM?

有Bootloader&#xff0c;为什么还要BROM? 不少硬件平台都提供类似Boot ROM或者PBL(高通平台)固化的一段程序&#xff0c;出厂后用户一定不能修改。BROM可以引导Bootloader程序。大家知道&#xff0c;每个可启动的平台都会在存储设备&#xff0c;例如EMMC/NAND/UFS保存Bootloa…...

【MATLAB代码】CV和CA模型组成的IMM(滤波方式为UKF),可复制粘贴源代码

该MATLAB代码实现了基于无迹卡尔曼滤波器(UKF)的交互式多模型(IMM)滤波算法,旨在跟踪目标在不同运动模式(匀速直线运动CV和匀速圆周运动CT)的位置和速度。订阅专栏后,直接复制粘贴代码到MATLAB空脚本中,即可运行 文章目录 运行结果源代码程序介绍1. 初始化和参数设定2…...

【网络】传输层协议TCP(下)

目录 四次挥手状态变化 流量控制 PSH标记位 URG标记位 滑动窗口 快重传 拥塞控制 延迟应答 mtu TCP异常情况 四次挥手状态变化 之前我们讲了四次挥手的具体过程以及为什么要进行四次挥手&#xff0c;下面是四次挥手的状态变化 那么我们下面可以来验证一下CLOSE_WAIT这…...

服务器数据恢复—EVA存储故障导致上层应用不可用的数据恢复案例

服务器存储数据恢复环境&#xff1a; 一台EVA某型号控制器EVA扩展柜FC磁盘。 服务器存储故障&检测&#xff1a; 磁盘故障导致该EVA存储中LUN不可用&#xff0c;导致上层应用无法正常使用。 服务器存储数据恢复过程&#xff1a; 1、将所有磁盘做好标记后从扩展柜中取出。硬…...

支持向量机相关证明 解的稀疏性

主要涉及拉格朗日乘子法&#xff0c;对偶问题求解...

静态ip和动态ip适合什么场景

静态住宅ip由于他的ip位置保持不变的&#xff0c;更加适合&#xff1a; 1、账号管理。 使用静态住宅来注册和管理社交媒体账号&#xff0c;例如facebook、领英等&#xff0c;包括电商类的账号也是可以的&#xff0c;例如亚马逊等 2、网站测试 很多网站会检测使用者是否为机器…...

Istio Gateway发布服务

1. Istio Gateway发布服务 在集群中部署一个 tomcat 应用程序。然后将部署一个 Gateway 资源和一个与 Gateway 绑定的 VirtualService&#xff0c;以便在外部 IP 地址上公开该应用程序。 1.1 部署 Gateway 资源 vim ingressgateway.yaml --- apiVersion: networking.istio.…...

前端vue3若依框架pnpm run dev启动报错

今天前端vue3若依框架pnpm run dev启动报错信息&#xff1a; > ruoyi3.8.8 dev D:\AYunShe\2024-11-6【无锡出门证】\wuxi-exit-permit-web > vite error when starting dev server: Error: listen EACCES: permission denied 0.0.0.0:80 at Server.setupListenHand…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...