【数据结构】神奇的二叉树


前言

在我第一次听到二叉树这个词的时候,脑海中就想起来下面的这个名场面,汤姆的“裤裆劈树”🤣🤣🤣

在这里插入图片描述
开个玩笑,在我们编程世界中,二叉树是一种特殊的 “树”,而要认识二叉树,我们有得先认识 “树” 是什么玩意


1. 树形结构

1.1 什么是树

树跟我们前面学到的数据结构都不一样,它为一种非线性的数据结构,是由 n(n>=0)个有限节点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树(根朝上,而叶朝下)

在这里插入图片描述


  • 根节点:没有前驱节点
  • 除了根节点,其余节点都可以被分成互不相交的子集合,而每个子集合又是一棵和树相似的子树。每棵子树的根节点都只有一个前驱节点,可以有零个或多个后继节点
  • 树是通过递归定义的

在这里插入图片描述

要注意:如果子树之间有交集,那就不能算是树了

1.2 名词概念

在这里插入图片描述

  • 节点(node):包含一个数据元素以及若干指向子树分支的信息
  • 节点的度:一个节点含有的子树的个数。上图中:C的度为2
  • 树的度:在一棵树中,最大的节点的度即为树的节点。上图中:树的度为2
  • 叶子节点:也叫做终端节点,度为零的节点。
  • 分支节点:也叫做非终端节点,度不为零的节点
  • 父节点:也叫做双亲节点。上图中:A为B的父节点
  • 子节点:也叫做孩子节点。上图中:D为B的子节点
  • 根节点:在一棵树中,没有双亲节点的节点
  • 节点的层次:根节点算作第1层,依次往下为2层、3层……
  • 树的高度或深度:树中节点的最大层次。(深度是相对节点位置的,而深度的最大值就等于树的高度)上图中:树的高度为4
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点。上图中:B和C是兄弟节点
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟节点;上图中:D和E是堂兄弟节点
  • 节点的祖先:从根到该节点所经分支上的所有节点。上图中:A是所有节点的祖先

1.3 树的表现形式

之前我们在学习线性表的时候,是定义了个节点Node类,类内部定义了 val 值和 next 指向下一个节点的地址。在二叉树这里,我们也是类似的表示形式。树可以用孩子表示法、孩子双亲表示法、孩子兄弟表示法等等,我们在这里就用孩子表示法

	class TreeNode {public char val; //节点中存储的数据public TreeNode left; //左孩子public TreeNode right; //右孩子
}

在这里插入图片描述


2. 二叉树

2.1 概念

一棵二叉树是一个有限的节点集合,该集合为:

  • 要么为空
  • 要么是有一个根节点加上左右两棵称为左子树右子树的二叉树组成
    在这里插入图片描述

上图就是典型的二叉树,我们可以看出:

  • 二叉树不存在度大于2的节点
  • 二叉树是一棵有序树,它的子树有左右之分,次序不能颠倒
    在这里插入图片描述

二叉树的五种基本形态

在这里插入图片描述

三种特殊的形态

在这里插入图片描述

接下来我们将重点讲解满二叉树完全二叉树


2.2 两种特殊的二叉树

满二叉树:如果每层的节点数都达到最大值,则这棵二叉树就是满二叉树。即如果二叉树的节点总数为 2*K - 1(K为树的层数),那它就是满二叉树

完全二叉树:除了最后一层,所有层的节点都被完全填满,而且最后一层的节点尽可能地集中在左侧。这样的二叉树就是完全二叉树(我们也可以这样理解:所有的叶子节点都在最后一层或者倒数第二层,且最后一层叶子节点在左边连续,倒数第二层在右边连续)

要注意:满二叉树就是一种特殊的完全二叉树


2.3 二叉树的性质

  1. 假设根节点的层数为第1层,那么一棵非空二叉树第i层上最多有 2 i − 1 2^{i-1} 2i1(i>0)个节点
  • 证明:因为二叉树的度最大为2,假设每一层节点的度都为2。那么第1层就有1个节点,第2层就有2个节点,第3层就有4个节点,根据等比数列的规律,我们可以算出第i层上最多有 2 i − 1 2^{i-1} 2i1(i>0)个节点
  1. 假设根节点的深度为1,那么深度为K的二叉树最大节点数为 2 K − 1 2^K-1 2K1(K>=0)
  • 证明:假设每一层节点的度都为2。那么第1层就有1个节点,第2层就有2个节点,第3层就有4个节点……根据等比数列的求和公式可以得到深度为K的二叉树最大节点数为 2 K − 1 2^K-1 2K1(K>=0)
  1. 对于任何一棵二叉树, n 0 n_0 n0表示叶子节点数,用 n 2 n_2 n2来表示度为2的节点数,则有 n 0 n_0 n0 = n 2 n_2 n2 + 1
  • 证明: n 1 n_1 n1表示度为1的节点数,又因为一棵N个节点的数有N-1条边,所以我们可以等出两条等式①N = n 0 n_0 n0 + n 1 n_1 n1 + n 2 n_2 n2 ②N = n 0 n_0 n0*0 + n 1 n_1 n1*1 + n 2 n_2 n2*2; 联立可得 n 0 n_0 n0 = n 2 n_2 n2 + 1
  1. 假设根节点的层数为第1层,那么有n个节点完全二叉树的深度为 log ⁡ 2 ( n + 1 ) \log_2{(n+1)} log2(n+1) 的向上取整
    证明:因为深度为K的满二叉树的节点数n一定小于等于 2 K − 1 2^K-1 2K1(用性质2可得),那么倒推可以等到 K = log ⁡ 2 ( n + 1 ) \log_2{(n+1)} log2(n+1) ,向上取整指的是
  2. 对于完全二叉树,如果我们从上到下、从左往右编号,则编号为 i 的节点,则其左孩子编号就为 2i,右孩子编号就为 2i+1;其双亲节点编号为 i/2(i = 1时为根节点,无双亲节点)

3. 二叉树的存储结构

二叉树有两种存储结构:顺序存储和链式存储

3.1 顺序存储

二叉树的顺序存储结构跟线性表十分相似,就是使用一维数组来存储二叉树中的节点,而数组的下标表示的就是该节点的存储位置:

在这里插入图片描述

该树各节点在数组中的形式:(为表示方便,此处起始点记为1)

在这里插入图片描述

我们上面展示的树为完全二叉树,它刚好可以填满整个数组,不会造成存储空间的浪费

而当二叉树不是完全二叉树时:(D、F表示不存在的节点)

在这里插入图片描述

其存储结构如下,^ 表示该位置没有节点,我们可以发现,此时浪费了两个存储空间

在这里插入图片描述

由此我们可以得到一个结论:顺序存储结构适用于完全二叉树,非完全二叉树使用顺序存储则会造成浪费存储空间。因此对于二叉树,我们更习惯于使用链式存储结构


3.2 链式存储

我们可以将节点设计成两个域:数据域和指针域,数据域用来存放具体数据,指针域则存放父节点或者子节点的地址。下面我们使用孩子表示法进行演示

在这里插入图片描述

通过一个一个的节点引用起来就是链式存储,常见的表示方式为二叉链表,如图

在这里插入图片描述


4. 二叉树的遍历

二叉树的遍历指的是从根节点出发,按照某种约定依次对树中的每个节点仅作一次访问。遍历是二叉树上最重要的操作之一,是二叉树上进行其他运算的基础。二叉树一共有四种遍历方式:

  • 前序遍历(NLR):又称为先序遍历,先访问根节点 → \rightarrow 根的左子树 → \rightarrow 根的右子树
  • 中序遍历(LNR):先访问根的左子树 → \rightarrow 根节点 → \rightarrow 根的右子树
  • 后序遍历(LRN):先访问根的左子树 → \rightarrow 根的右子树 → \rightarrow 根节点
  • 层序遍历:从根节点从上往下逐层遍历,在同一层时,按从左到右的顺序对节点逐个访问。与上面提到的顺序存储结构相类似

N:Node(根节点) L:Left(左子树) R:Right(右子树)


4.1 前序遍历

规则:首先访问根节点,然后递归地进行左子树的前序遍历,最后递归地进行右子树的前序遍历

此处要重点理解递归的含义:我们知道,二叉树是递归定义的,即每棵子树都可以看成是一棵二叉树。所以在遍历的时候,需要不断对新的子树严格按照前序遍历的规则来执行

具体的遍历步骤如下:

  1. 访问根节点:首先访问当前节点,也就是根节点
  2. 遍历左子树:然后,对根节点的左子节点进行前序遍历。如果左子节点存在,重复上述步骤,即先访问左子节点,然后递归地遍历其左子树,接着遍历其右子树
  3. 遍历右子树:最后,对根节点的右子节点进行前序遍历。同样,如果右子节点存在,重复上述步骤

在这里插入图片描述

由此我们可以得到前序遍历的结果为:

A B D H E I C F J G


4.2 中序遍历

首先递归地进行左子树的中序遍历,然后访问根节点,最后递归地进行右子树的中序遍历

具体的遍历步骤如下:

  1. 遍历左子树:首先,对根节点的左子节点进行中序遍历。如果左子树存在,那么按照同样的规则,先遍历左子树的左子树,然后访问左子树的根节点,最后遍历左子树的右子树
  2. 访问根节点:在左子树的遍历完成后,访问当前节点,也就是根节点
  3. 遍历右子树:最后,对根节点的右子节点进行中序遍历。如果右子树存在,重复上述步骤,即先遍历右子树的左子树,然后访问右子树的根节点,最后遍历右子树的右子树

在这里插入图片描述

由此我们可以得到中序遍历的结果为:

H D B I E A F J C G


4.3 后序遍历

首先递归地进行左子树的后序遍历,然后递归地进行右子树的后序遍历,最后访问根节点

具体的遍历步骤如下:

  1. 遍历左子树:首先,对根节点的左子节点进行后序遍历。如果左子树存在,那么按照同样的规则,先遍历左子树的左子树,然后遍历左子树的右子树,最后访问左子树的根节点
  2. 遍历右子树:在左子树的遍历完成后,对根节点的右子节点进行后序遍历。如果右子树存在,重复上述步骤,即先遍历右子树的左子树,然后遍历右子树的右子树,最后访问右子树的根节点
  3. 访问根节点:在左子树和右子树的遍历都完成后,访问当前节点,也就是根节点

在这里插入图片描述

由此我们可以得到后序遍历的结果为:

H D I E B J F C G A


4.4 层序遍历

从根节点从上往下逐层遍历,在同一层,按从左到右的顺序对节点逐个访问

在这里插入图片描述

层序遍历的结果为:

A B C D E F G H I J


通过上面的例子我们也知道了二叉树的遍历是怎么一回事。实际上,我们也可以根据遍历的结果来创建出一棵二叉树:

前序遍历 + 中序遍历、 后序遍历 + 中序遍历

前序遍历的第一个节点就是根节点;后序遍历的最后一个节点也是根节点。在知道根节点后,根据中序遍历的我们就能得知左子树和右子树,最后根据递归的规律我们就能够反推出一棵二叉树

但是如果只知道前序遍历和后序遍历则是无法反推出一棵二叉树


5. 遍历的代码实现

5.1 递归实现

因为二叉树是由递归定义的,所以我们最常使用递归来实现二叉树的遍历

首先,我们要先定义好节点:(此处使用孩子表示法)

	class TreeNode {public char val; //节点中存储的数据public TreeNode left; //左孩子public TreeNode right; //右孩子public TreeNode(char val) {this.val = val;}}

前序遍历:

    //前序遍历(根左右)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 + " ");//根}

层序遍历:(此处我们需要借助队列)

从根节点从上往下逐层遍历,在同一层,按从左到右的顺序对节点逐个访问

    //层序遍历public void levelOrder(TreeNode root) {Queue<TreeNode> queue = new LinkedList<>();if (root == null) {return;}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);}}System.out.println();}

设计思路:队列有先进先出的特性。

  1. 首先我们得判断根节点是否为 null,为 null 就直接返回,说明是一棵空树,不为 null 就入队
  2. 接着就是以队列是否为空来作为 while 循环条件,不为空就一直循环。循环内部我们让根节点出队,创建一个 cur 来接收根节点,接下来打印 cur 的值
  3. 然后就判断 cur 的左右是否为null(一定要先左再右),不为 null 就入队。接着继续循环上面操作,cur 接收出队的节点,打印 cur 的值
  4. 最后队列为空,循环停止,层序遍历完成

5.2 非递归实现(了解)

非递归实现遍历需要借助栈,它有先进后出的特性

    //前序遍历(非递归)public void preOrderNot(TreeNode root) {Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;while (cur != null || !stack.empty()) {while (cur != null) {stack.push(cur);System.out.print(cur.val + " ");cur = cur.left;}TreeNode top = stack.pop();cur = top.right;}}//中序遍历(非递归)public void inOrderNot(TreeNode root) {Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;while (cur != null || !stack.empty()) {while (cur != null) {stack.push(cur);cur = cur.left;}TreeNode top = stack.pop();System.out.print(top.val + " ");cur = top.right;}}//后序遍历(非递归)public void postOrderNot(TreeNode root) {Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;TreeNode prev = null;while (cur != null || !stack.isEmpty()) {while (cur != null) {stack.push(cur);cur = cur.left;}TreeNode top = stack.peek();if (top.right == null || top.right == prev ) {stack.pop();System.out.print(top.val+" ");prev = top;} else {cur = top.right;}}}

通过对比代码数量我们也可以看出非递归实现二叉树的遍历十分麻烦,因此该方法了解即可


结语

二叉树的相关知识十分重要,关于四种遍历的递归思路一定要熟记。下一篇博客我会详细介绍二叉树的经典题型,如 ”相同的二叉树‘ “翻转二叉树”……掌握了这些经典题型能让我们更加深刻的认识二叉树
希望大家能喜欢这篇文章,有总结不到位的地方还请多多谅解,若有出现纰漏,希望大佬们看到错误之后能够在私信或评论区指正,博主会及时改正,共同进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/15144.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PTT票据传递攻击

一. PTT票据传递攻击原理 1.PTT介绍 PTT(Pass The Ticket)&#xff0c;中文叫票据传递攻击&#xff0c;PTT 攻击只能用于kerberos认证中,NTLM认证中没有&#xff0c; PTT是通过票据进行认证的。 进行票据传递&#xff0c;不需要提权&#xff0c;域用户或者system用户就可以 …

【图书推荐】《Vue.js 3.x+Element Plus从入门到精通(视频教学版)》

本书用处 内容简介 本书通过对Vue.js&#xff08;简称Vue&#xff09;的示例和综合案例的介绍与演练&#xff0c;使读者快速掌握Vue.js 3.x框架的用法&#xff0c;提高Web前端的实战开发能力。本书配套示例源码、PPT课件、教学大纲、教案、同步教学视频、习题及答案、其他资源…

HTML+CSS 响应式导航栏

效果演示 Code <div class="navbar"><input type="checkbox"><span></span><span></span><ul><li><a href="#">点赞</a></li><li><a href="#">关注&…

SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?

尼恩&#xff1a;LLM大模型学习圣经PDF的起源 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;经常性的指导小伙伴们改造简历。 经过尼恩的改造之后&#xff0c;很多小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试机会&#x…

【数据结构】数据结构中的隐藏玩法——栈与队列

前言&#xff1a; 哈喽大家好&#xff0c;我是野生的编程萌新&#xff0c;首先感谢大家的观看。数据结构的学习者大多有这样的想法&#xff1a;数据结构很重要&#xff0c;一定要学好&#xff0c;但数据结构比较抽象&#xff0c;有些算法理解起来很困难&#xff0c;学的很累。我…

前端基于word模板导出word文档

项目环境 vue2 js vue-cli等 依赖包都可以在npm官网找到对应文档 npm官网(英文) 1、依赖 安装依赖 docxtemplater npm i docxtemplaterfile-saver npm i file-saverjszip-utils npm i jszip-utilsjszip npm i jszip在对应页面或模块中引入依赖 import Docxtemplater …

QQ个性网空间日志网站模板源码

QQ个性网空间日志网站模板源码自带后台登录设置&#xff0c;适用于博客、文章、资讯、其他类网站内容使用。模板自带eyoucms内核&#xff0c;原创设计、手工书写DIVCSS&#xff0c;完美兼容IE7、Firefox、Chrome、360浏览器等;主流浏览器;结构容易优化;多终端均可正常预览。由于…

力扣刷题---2206. 将数组划分成相等数对【简单】

题目描述&#x1f357; 给你一个整数数组 nums &#xff0c;它包含 2 * n 个整数。 你需要将 nums 划分成 n 个数对&#xff0c;满足&#xff1a; 每个元素 只属于一个 数对。 同一数对中的元素 相等 。 如果可以将 nums 划分成 n 个数对&#xff0c;请你返回 true &#xf…

力扣刷题---3146. 两个字符串的排列差

题目描述 给你两个字符串 s 和 t&#xff0c;每个字符串中的字符都不重复&#xff0c;且 t 是 s 的一个排列。 排列差 定义为 s 和 t 中每个字符在两个字符串中位置的绝对差值之和。 返回 s 和 t 之间的 排列差 。 示例 1&#xff1a; 输入&#xff1a;s “abc”, t “b…

运用HTML、CSS设计Web网页——“西式甜品网”图例及代码

目录 一、效果展示图 二、设计分析 1.整体效果分析 2.头部header模块效果分析 3.导航及banner模块效果分析 4.分类classify模块效果分析 5.产品展示show模块效果分析 6.版权banquan模块效果分析 三、HTML、CSS代码分模块展示 1. 头部header模块代码 2.导航及bann…

【SpringCloud】Spring Cloud基本介绍

目录 回顾架构分类单体架构分布式架构微服务架构什么是微服务优点缺点微服务的架构特征&#xff1a;微服务架构面临的挑战技术挑战微服架构的设计原则微服务概念提供者(Provider)消费者(Consumer)RPC和Restful集群分布式 总结 服务拆分和远程调用服务拆分原则服务拆分示例 思考…

http项目改为/支持https的方案、无需修改后台代码

背景描述&#xff1a;原来的项目前后台都是http&#xff0c;现在某个服务要求前台必须使用https&#xff1b; 方案1&#xff1a;前台部署在https里&#xff0c;后面代码修改&#xff1b;但是微服务架构&#xff0c;后台工作量太大&#xff1b; 方案2&#xff1a;前台部署在ht…

Pytorch-08 实战:手写数字识别

手写数字识别项目在机器学习中经常被用作入门练习&#xff0c;因为它相对简单&#xff0c;但又涵盖了许多基本的概念。这个项目可以视为机器学习中的 “Hello World”&#xff0c;因为它涉及到数据收集、特征提取、模型选择、训练和评估等机器学习中的基本步骤&#xff0c;所以…

vue 打印、自定义打印、页面打印、隐藏页眉页脚

花了一天时间搞了个打印功能&#xff0c;现则将整体实现过程进行整理分享。先来看看效果图&#xff1a; 1、页面展示为&#xff1a; 2、重组页面打印格式为&#xff1a;这里重组页面的原因是客户要求为一行两列打印 &#xff01;内容过于多的行则独占一行显示完整。 整体实现&…

区块链论文总结速读--CCF A会议 USENIX Security 2024 共7篇 附pdf下载

Conference&#xff1a;33rd USENIX Security Symposium CCF level&#xff1a;CCF A Categories&#xff1a;网络与信息安全 Year&#xff1a;2024 Num&#xff1a;7 1 Title: Practical Security Analysis of Zero-Knowledge Proof Circuits 零知识证明电路的实用安全…

hbase版本从1.2升级到2.1 spark读取hive数据写入hbase 批量写入类不存在问题

在hbase1.2版本中&#xff0c;pom.xml中引入hbase-server1.2…0和hbase-client1.2.0就已经可以有如下图的类。但是在hbase2.1.0版本中增加这两个不行。hbase-server2.1.0中没有mapred包&#xff0c;同时mapreduce下就2个类。版本已经不支持。 <dependency><groupId>…

安全访问python字典:避免空键错误的艺术

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、直接访问字典键的问题 三、使用get方法安全访问字典键 四、get方法的实际应…

Could not create connection to database server的错误原因

1、使用MyBatis 连接数据库报错 org.apache.ibatis.exceptions.PersistenceException: ### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server. ### The error may …

用队列实现栈,用栈实现队列

有两个地方会讨论到栈&#xff0c;一个是程序运行的栈空间&#xff0c;一个是数据结构中的栈&#xff0c;本文中讨论的是后者。 栈是一个先入后出&#xff0c;后入先出的数据结构&#xff0c;只能操作栈顶。栈有两个操作&#xff0c;push 和 pop&#xff0c;push 是向将数据压…

电脑如何远程监控?如何远程监控电脑屏幕?

远程监控是指通过网络技术和远程视频传输技术&#xff0c;实现对某一特定区域、设备或场景进行远程实时监测、管理、控制的一种技术手段。 它将视频传输、图像采集、数据存储和远程操作等多种技术相结合&#xff0c;能够在任意时间、任意地点实现对被监测对象的远程监控。 远程…