【数据结构】树形结构--二叉树

【数据结构】树形结构--二叉树

  • 一.知识补充
    • 1.什么是树
    • 2.树的常见概念
  • 二.二叉树(Binary Tree)
    • 1.二叉树的定义
    • 2.二叉树的分类
    • 3.二叉树的性质
  • 三.二叉树的实现
    • 1.二叉树的存储
    • 2.二叉树的遍历
      • ①.先序遍历
      • ②.中序遍历
      • ③.后序遍历
      • ④.层序遍历

一.知识补充

1.什么是树

如图是一个现实生活中的树,观察可以发现,一棵树只有一个主干,而主干又会分出许多枝干,这些枝干可能会再分出更多枝干,最后以叶子结束。
在这里插入图片描述
树型结构在现实世界广泛存在,如人类社会的族谱和各种社会组织机构都可以用树来形象表示。

数据结构中的树与现实的树类似,下图中的三种都是数据结构中的树。在这里插入图片描述

树也是由结点构成的有限集合。我们将树定义为:
①.有且仅有一个结点被称为根结点(root)
②.剩余结点又可成为互不相交的集合,每个集合本身是一个树,也是根结点的子树。

2.树的常见概念

根据此图,为大家介绍一下树的常见概念。
在这里插入图片描述

结点的度:每个结点拥有的子树个数。

图中树的A结点的度为3,B结点的度为2。

叶子结点(终端结点):度为0的结点,即没有子树的结点。

图中树的叶子结点有K、L、F、G、M、I、J。

子结点(孩子结点):a结点是b结点的子树的根,则a结点是b结点的孩子结点。

图中树的B、C、D结点都是A结点的子结点。

双亲结点(父结点):a结点是b结点的子结点,那么b结点就是a结点的双亲结点。

图中树的A结点是B、C、D结点的双亲结点。

兄弟结点:具有相同父节点的结点被称为兄弟节点。

图中树的B、C、D结点即为兄弟结点。

树的度:一棵树中所有结点的度中最大的度。

图中树的度为3,因为最大的度是A结点和D结点。

结点的层次:从根开始定义,根结点为第一层,根的孩子为第二层,孩子的孩子为第三层,以此类推。(也有的是将根结点定义为第0层,依次相加。)

树的深度(高度):结点层次中最大的那个。

图中树的深度即为4。

祖先:从根结点到某个结点路径上的所有结点都是该结点的祖先。

图中M结点的祖先有H、D、A结点。

子孙:以某结点为根的树中的所有结点都是它的子孙。

图中E、F、K、L结点都是B结点的祖孙。

有序树、无序树:树的各个子树从左至右是有顺序的,不能改变,即称为有序树,反之为无序树。

森林:由多棵互不相交的树构成的集合。

二.二叉树(Binary Tree)

1.二叉树的定义

二叉树是一种特殊的树,它的特点是每个结点最多只有两棵子树,并且子树的左右顺序不能改变。

图中五种都属于二叉树。
在这里插入图片描述

2.二叉树的分类

①.一般二叉树:

每个结点最多有两个子结点,无其他要求,结构灵活。

如图就是一棵普通二叉树。
在这里插入图片描述

②.满二叉树:

除了叶子结点,其余所有结点度均为2,不存在度为1的结点。

如图就是一棵满二叉树。
在这里插入图片描述
如果一棵满二叉树层数为k,那么总结点个数就是2k - 1(等比数列求和)。

③.完全二叉树:

对一棵具有n个结点的二叉树按层序编号,每一个编号为i(1≤i≤n)的结点与同样深度的满二叉树中编号为i 的结点的位置完全相同。

如图右侧就是一棵完全二叉树。
在这里插入图片描述
我们会发现完全二叉树就像满二叉树从最后一个结点开始向左任意删除n个结点。完全二叉树最后一层可能不满,但从左往右结点一定是连续的,因此完全二叉树只存在一个度为1的结点。
如图就不是一棵完全二叉树,因为最后一层叶子结点并非连续的。
在这里插入图片描述
满二叉树是一种特殊的完全二叉树。

④.二叉排序树(二叉查找树):

左子树结点的值均小于根结点,右子树的值均大于根结点。左右子树又各是一棵二叉排序树。

如图是一棵二叉排序树。
在这里插入图片描述

二叉排序树常用于元素的搜索和排序。

⑤.平衡二叉树:

平衡二叉树上任意一个结点的左右子树的高度差不会超过1。

如图就是一棵平衡二叉树。
在这里插入图片描述
平衡二叉树与二叉查找树相结合可以提高搜索效率。

⑥.其他二叉树:
线索二叉树(Threaded Binary Tree):通过空指针指向先驱或后继节点,优化遍历。
哈夫曼树(Huffman Tree):用于数据压缩,按频率构建的带权二叉树。等等

3.二叉树的性质

①.一棵二叉树的第i层上,最多有2i-1个结点。
②.一棵二叉树如果总共有k层,那么它最多有2k-1个结点。
③.一棵二叉树如果其度为0的结点个数为n0,度为2的结点个数为n2,那么满足n0=n2+1。

三.二叉树的实现

在逻辑上,树是用递归定义的,而二叉树的各种操作也是使用递归实现的,因此对递归还不熟悉的朋友建议先学习一下递归,否则可能较难上手。

1.二叉树的存储

我们在实现数据元素的存储时,应当明确的是,对于树来说,它的存储应当着重关注数据元素以及数据元素之间的逻辑关系在存储器中的表示。说人话就是,如何表示树的结点之间的逻辑关系,即如何表示结点的双亲和孩子。
二叉树的存储也有顺序存储和链式存储两种。(树的存储结构常用链表结构)

顺序存储:
一棵二叉树的顺序存储就是用一组地址连续的存储单元依次自上而下、自左至右存储树上的结点。

如图是一棵完全二叉树
在这里插入图片描述
如上所示,这棵树的结点的编号就是按照从上到下、从左到右来编号。在数组中存储时,就是按照下面这样的方式来去顺序存储:(数组中的内容是相应编号的结点数据)。
在这里插入图片描述
借着完全二叉树,我们可以理解一般二叉树的顺序存储。
如图是一棵普通二叉树
在这里插入图片描述
我们在进行存储时先将它补全:
在这里插入图片描述
那么在数组中的就是这样存储的:
在这里插入图片描述
这种存储方式有较明显的缺点,如图是一棵很不平衡的二叉树:
在这里插入图片描述
它仅有四个结点,但在数组中存储时却浪费了较大空间。

链式存储:
因为二叉树最多有两个孩子结点,只有一个双亲结点,因此在定义链式存储时有两种表示:二叉链、三叉链。

二叉链:有两个指针域,一个指向左孩子,另一个指向右孩子。
在这里插入图片描述

其结构表示为:

typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* leftchild;struct BinaryTreeNode* rightchild;
}BTNode;

三叉链:有三个指针域,一个指向双亲结点,另外两个指向左、右孩子结点。
在这里插入图片描述

其结构表示为;

typedef struct BinaryTreeNode
{BTDataType data;struct BinaryTreeNode* leftchild;struct BinaryTreeNode* rightchild;struct BinaryTreeNode* parent;
}BTNode;

普通二叉树一般用二叉链结构表示,特殊二叉树(AVL树、红黑树、B树等)会采用三叉链结构来表示。

本文使用二叉链式结构来实现二叉树。由于二叉树是由根结点,左子树,右子树构成,其中左子树也包含它的根结点,左子树和右子树。

2.二叉树的遍历

二叉树的遍历是指从根结点出发按照某种次序依次访
问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次。

对于线性结构,遍历是很简单的事。一个数组我们可以从头到尾依次遍历,一个链表我们可以根据结点的指向来遍历,那么对于一个有多分支的树形结构,我们如何遍历一棵二叉树呢?可以通过以下四种方法。

①.先序遍历

先序遍历也叫前序遍历,就是根结点在前,按照根->左->右的顺序递归遍历一棵树。
若二叉树为空,则返回;否则:①访问根结点;②前序遍历根结点的左子树;③前序遍历根结点的右子树。

由于每个结点又包含左子树和右子树,因此遍历完该结点,遍历它的左子树时,同样要进入左子树的左子树,按照根->左->右的顺序遍历,直到最底层的结点的左右子树都遍历完,再向上返回遍历其他结点。
以图中这棵树为例,详细讲解一下先序遍历的过程。
在这里插入图片描述
先序遍历这棵树,先访问根结点,得到A,然后遍历A的左子树:在这里插入图片描述
对于该左子树,同样先访问根结点,得到A->B,再遍历B的左子树
在这里插入图片描述
同样先访问根结点,得到A->B->D,再遍历D的左子树,左子树为空,然后遍历D的右子树,得到A->B->D->G。
B这棵左子树递归遍历完之后依次向上返回,进入A的右子树。
在这里插入图片描述
同样先访问根结点,得到A->B->D->G->C,进入C的左子树
在这里插入图片描述
同样先访问根结点,得到A->B->D->G->C->E,然后遍历左子树,为空,再遍历右子树,为空,返回。
C的左子树遍历完,进入C的右子树
在这里插入图片描述
同样先访问根结点,得到A->B->D->G->C->E->F,然后遍历左子树,为空,再遍历右子树,为空,返回。

至此,整棵树已经完全被遍历完,顺序如图
在这里插入图片描述

得到先序遍历结果是:A->B->D->G->C->E->F。

代码实现为:

//先序遍历
void PreOrder(BTNode* root)
{//根结点为空,即树为空时,直接返回if (root == NULL)return;printf("%c ", root->data);  //遍历根结点PreOrder(root->leftchild);  //遍历左子树PreOrder(root->rightchild); //遍历右子树}

②.中序遍历

中序遍历即根结点在中间,按照左->根->右的顺序递归遍历一棵树,即:①中序遍历根结点的左子树;②访问根结点;③中序遍历根结点的右子树。

每个结点都包含左子树和右子树。在遍历时从根结点进入它的左子树,再进入左子树的左子树,直到进入最后一个左子树,访问该左子树的左孩子,然后访问根结点,然后访问右孩子,再依次向上返回遍历剩下结点。
依旧按照这棵树为例,详细讲述中序遍历的过程。
在这里插入图片描述
中序遍历这棵树,首先根据根结点进入A的左子树:
在这里插入图片描述
B的左子树不为空,再进入B的左子树:
在这里插入图片描述
发现D的左子树为空,返回,然后访问根结点得到D,然后进入D的右子树:
在这里插入图片描述

发现G的左子树为空,返回,访问根结点得到D->G,G的右子树也为空,返回。
此时B的左子树遍历完,访问B这个结点,得到D->G->B,
然后进入B的右子树,为空,返回。
A的左子树已经全部遍历完,访问A这个结点,得到D->G->B->A,然后进入A的右子树:
在这里插入图片描述
C的左子树不为空,进入C的左子树:
在这里插入图片描述
发现E的左子树为空,返回,然后访问B结点,得到D->G->B->A->E,E的右子树也为空,返回。
C的左子树遍历完,访问C结点,得到D->G->B->A->E->C,然后进入C的右子树:在这里插入图片描述
F的左子树为空,返回,访问F结点得到D->G->B->A->E->C->F,F的右子树为空,返回。
此时C这棵树已经全部遍历完,向上返回,A这棵树也全部遍历完,中序遍历结束,顺序为:
在这里插入图片描述

遍历结果为D->G->B->A->E->C->F。

代码实现为:

//中序遍历
void InOrder(BTNode* root)
{if (root == NULL)return;InOrder(root->leftchild);//先遍历左子树printf("%c ", root->data);//然后是根InOrder(root->rightchild);//最后是右子树
}

③.后序遍历

后序遍历即按照左->右->根的顺序,最后访问根结点的操作,即:①后序遍历根结点左子树;②后序遍历根结点的右子树;③最后访问根结点。

依旧以该树为例进行后序遍历,这次画图演示,不做详细说明。
在这里插入图片描述
在这里插入图片描述
得到的后序遍历结果为:G->D->B->E->F->C->A。
代码实现为:

//后序遍历
void PostOrder(BTNode* root)
{if (root == NULL)return;PostOrder(root->leftchild); //先遍历左子树PostOrder(root->rightchild);//再遍历右子树printf("%c ", root->data);  //最后遍历根结点
}

④.层序遍历

层序遍历即一层一层地依次访问每个结点。

如图即为层序遍历的顺序:
在这里插入图片描述
层序遍历是借助队列这个数据结构来实现的(对队列不清楚的可以先看看我这篇讲述队列的博客链接: 【数据结构】–队列。)

首先将根结点加入队列,当队列不为空时,取出队头结点,访问该结点,然后将队头结点的左孩子,右孩子加入队列。循环执行该操作,直到队列所有元素都取出,队列为空时停止。
代码实现为:

//层序遍历
void LevelOrder(BTNode* root)
{//借助队列完成Queue q;QueueInit(&q);if (root == NULL)return;QueuePush(&q, root);while (QueueSize(&q)){BTNode* tmp = QueueFront(&q);QueuePop(&q);if (tmp->leftchild)QueuePush(&q, tmp->leftchild);if (tmp->rightchild)QueuePush(&q, tmp->rightchild);printf("%c ", tmp->data);}
}

OK,这篇文章先讲到这里,二叉树内容有点多且相对较难,剩下的操作下篇文章再详细介绍。
感谢阅读!^ _ ^
在这里插入图片描述

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

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

相关文章

从认识AI开始-----解密LSTM:RNN的进化之路

前言 我在上一篇文章中介绍了 RNN,它是一个隐变量模型,主要通过隐藏状态连接时间序列,实现了序列信息的记忆与建模。然而,RNN在实践中面临严重的“梯度消失”与“长期依赖建模困难”问题: 难以捕捉相隔很远的时间步之…

接地气的方式认识JVM(一)

最近在学jvm,浮于表面的学了之后,发现jvm并没有我想象中的那么神秘,这篇文章将会用接地气的方式来说一说这些jvm的相关概念以及名词解释。 带着下面两个问题来阅读 认识了解JVM大致有什么在代码运行时的都在背后做了什么 JVM是个啥&#xf…

Next.js 15 与 Apollo Client 的现代集成及性能优化

Next.js 15 与 Apollo Client 的现代集成及性能优化 目录 技术演进集成实践性能优化应用案例未来趋势 技术演进 Next.js 15 核心特性对开发模式的革新 Next.js 15 通过引入 App Router、服务器组件(Server Components)和客户端组件(Clie…

无人机桥梁3D建模、巡检、检测的航线规划

无人机桥梁3D建模、巡检、检测的航线规划 无人机在3D建模、巡检和检测任务中的航线规划存在显著差异,主要体现在飞行高度、航线模式、精度要求和传感器配置等方面。以下是三者的详细对比分析: 1. 核心目标差异 任务类型主要目标典型应用场景3D建模 生成…

Hive数据倾斜问题深度解析与实战优化指南

一、数据倾斜现象的本质与危害 数据倾斜是Hive在MapReduce计算过程中,​部分Key对应的数据量远超其他Key,导致少数Reducer任务处理时间远高于其他任务的性能瓶颈问题。典型表现为: ​作业进度卡在99%​​:99%的Reducer已完成,剩余1%持续数小时​资源利用率失衡​:部分节…

VRRP 原理与配置:让你的网络永不掉线!

VRRP 原理与配置:让你的网络永不掉线! 一. VRRP 是什么,为什么需要它?二. VRRP 的核心概念三. VRRP 的工作原理四. 华为设备 VRRP 配置步骤 (主备模式)4.1 拓扑示例4.2 🛠 配置步骤 五. VRRP 配…

解决开发者技能差距:AI 在提升效率与技能培养中的作用

企业在开发者人才方面正面临双重挑战。一方面,IDC 预测,到2025年,全球全职开发者将短缺400万人;另一方面,一些行业巨头已暂停开发者招聘,转而倚重人工智能(AI)来满足开发需求。这不禁…

痛点即爆点?如何挖掘客户的痛点和需求?

销售的核心在于精准洞察客户需求与痛点,并运用专业能力为其提供定制化解决方案,从而消除客户顾虑、解决问题,最终实现双赢。而快速识别客户痛点,不仅是成交的关键,更是建立专业形象、赢得客户信任的核心能力。那么&…

云服务器如何自动更新系统并保持安全?

云服务器自动更新系统是保障安全、修补漏洞的重要措施。下面是常见 Linux 系统(如 Ubuntu、Debian、CentOS)和 Windows 服务器自动更新的做法和建议: 1. Linux 云服务器自动更新及安全维护 Ubuntu / Debian 系统 手动更新命令 sudo apt up…

fvm install 下载超时 过慢 fvm常用命令、flutter常用命令

Git 配置问题 确保 Git 使用的是 HTTPS,而不是 SSH。如果你有 .gitconfig,确保没有配置奇怪的代理: git config --global --get http.proxy git config --global --get https.proxy如果有代理设置且不需要,取消代理:…

多语种OCR识别系统,引领文字识别新时代

在全球化与数字化深度融合的今天,语言障碍成为企业跨国协作、信息管理的一大挑战。无论是跨国合同签署、多语言档案管理,还是跨境商务沟通,高效精准的文字识别技术已成为刚需。中安智能OCR多语种识别系统应运而生,凭借其强大的光学…

Pyenv 使用指南:多版本 Python 环境管理

目录 Pyenv 是什么?安装 Pyenv管理 Python 版本虚拟环境管理项目级 Python 版本控制高级技巧常见问题解决最佳实践 Pyenv 是什么? Pyenv 是一个强大的 Python 版本管理工具,允许你: 在同一台机器上安装多个 Python 版本轻松切换…

Windows 11 家庭版 安装Docker教程

Windows 家庭版需要通过脚本手动安装 Hyper-V 一、前置检查 1、查看系统 快捷键【winR】,输入“control” 【控制面板】—>【系统和安全】—>【系统】 2、确认虚拟化 【任务管理器】—【性能】 二、安装Hyper-V 1、创建并运行安装脚本 在桌面新建一个 .…

leetcode:479. 最大回文数乘积(python3解法,数学相关算法题)

难度:简单 给定一个整数 n ,返回 可表示为两个 n 位整数乘积的 最大回文整数 。因为答案可能非常大,所以返回它对 1337 取余 。 示例 1: 输入:n 2 输出:987 解释:99 x 91 9009, 9009 % 1337 …

VR看房系统,新生代看房新体验

VR看房系统的概念 虚拟现实(VirtualReality,VR)看房系统,是近年来随着科技进步在房地产行业中兴起的一种创新看房方式。看房系统利用先进的计算机技术模拟出一个三维环境,使用户能够身临其境地浏览和体验房源,无需亲自…

栈与队列:数据结构的有序律动

在数据结构的舞台上,栈与队列宛如两位优雅的舞者,以独特的节奏演绎着数据的进出规则。它们虽不像顺序表与链表那般复杂多变,却有着令人着迷的简洁与实用,在众多程序场景中发挥着不可或缺的作用。今天,就让我们一同去探…

Flutte ListView 列表组件

目录 1、垂直列表 1.1 实现用户中心的垂直列表 2、垂直图文列表 2.1 动态配置列表 2.2 for循环生成一个动态列表 2.3 ListView.builder配置列表 列表布局是我们项目开发中最常用的一种布局方式。Flutter中我们可以通过ListView来定义列表项,支持垂直和水平方向展示…

跟Gemini学做PPT-模板样式的下载

好的,这里有一些推荐的网站,您可以在上面找到PPT目录样式和模板的灵感: SlideModel (slidemodel.com) 提供各种预先设计的目录幻灯片模板。这些模板100%可编辑,可用于PowerPoint和Google Slides。您可以找到不同项目数量&#xff…

【Netty系列】Reactor 模式 1

目录 一、Reactor 模式的核心思想 二、Netty 中的 Reactor 模式实现 1. 服务端代码示例 2. 处理请求的 Handler 三、运行流程解析(结合 Reactor 模式) 四、关键点说明 五、与传统模型的对比 六、总结 Reactor 模式是 Netty 高性能的核心设计思想…

LDAP(Lightweight Directory Access Protocol,轻量级目录访问协议)认证

理解 LDAP(Lightweight Directory Access Protocol,轻量级目录访问协议)认证,核心在于将其看作一种用于查询和验证用户身份信息的标准协议,类似于一个专门为“查找”优化的电子电话簿系统。以下是分层解析:…