🤡博客主页:醉竺
🥰本文专栏:《数据结构与算法》
😻欢迎关注:感谢大家的点赞评论+关注,祝您学有所成!
✨✨💜💛想要学习更多数据结构与算法点击专栏链接查看💛💜✨✨
目录
一.树的概念和结构
1.1 树的概念
在日常生活中,树是一种随处可见的植物,它由树根、树枝、树叶这些主要结构组成。而“树 形结构”,就是基于日常生活中的树而得名的一种非线性数据结构。
什么是“非线性的数据结构”?在实际场景中,常常存在着一对多,甚至是多对多的情况。想象一下,一根树枝可以分叉出很多树枝、树叶,我们也可 以 将“非线性的数据结构”理解成一种一对多的关系,而不是像一条线一样,按顺序排列的一对一关系。 例如下面两个生活中的例子就符合树形结构。
在数据结构中,树的定义为:树是一种非线性的数据结构,它是由n(n>=0)个有限节点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
- 当 n=0 时,树就是一棵空树;
- 如果树为非空,那么就会有两种情况:
- 一种,就是 n=1 的时候,有且只有唯一一个称为根 / 树根(Root)的特殊节点;
- 另一种就是,当 n>1 时,除根节点外,其余节点可以分为 m(m>0)个互不相交的有限集 T1、T2、…、Tm,其中每个集合自身又是一棵树,它们叫做根的子树,因此,树是递归定义的。上图中节点 A 就是树根 / 根节点
- 注意,一棵树可以只有树根而没有其他节点。
如下图,树的节点数量(n>1时)的情形。该树除了树根之后,又分成了3个互不相交的集合T1,T2和T3,这3个集合本身又各是一棵树,称为根的子树。
根据树的定义,我们可以知道,树的数据结构有如下特点:
树的特点:
- 树的定义是递归的,是一种递归的数据结构
- 树在逻辑结构上属于非线性结构
- 树的根节点没有前驱结点,除根节点以外的所有结点有且只有一个前驱结点
- 树的叶结点没有后继结点,除叶结点以外的所有结点都有一个或多个后继结点
注意:树形结构中,子树之间不能有交集,否则就不是树形结构!
1.2 树相关的基本术语
以下基本术语不需要死记硬背,只需要理解就好,将来忘记了可以随时来回顾。但是也一定要比对着图片耐心读一遍,理解着记忆。
- 节点拥有的子树的个数,叫做节点的度(Degree),比如上图中节点 A 的度是 2,节点 B 的度为 3,节点 C 的度为 1。
- 如果度为 0,那么这个节点就叫做叶节点(Leaf)或终端节点,上图中节点 H、I、J、K、 G 节点都是叶节点。相反,度不为 0 的节点称为分支节点或非终端节点。除根节点外的分支节点也称为内部节点。
- 树的度是树内各节点度的最大值,上图中节点 B 或者节点 D 的度都为 3,是节点度的最大 值,因此,树的度也是 3。
- 节点的子树的根称为该节点的子节点(Child),上图中节点 A 的子节点是节点 B、C,而节点 B 的子节点是节点 D、E、F。同样,节点 B、C 的父节点(Parent)或双亲节点就是节点 A,节点 D、E、F 的父节点就是节点 B。
- 节点的层次(Level)是从树根开始算起的,根为第一层,根的孩子为第二层,以此类推, 某个节点位于第 i 层,其子树就位于第 i+1 层。图 1 中节点 A 为第一层,节点 B、C 为第 二层,节点 D、E、F、G 为第三层,节点 H、I、J、K 为第四层。树的高度或深度 (Depth)是树中节点最大层数,因此图 1 树的深度是 4。
- 相同父节点的孩子之间互称兄弟节点(Sibling),比如 D、E 节点是兄弟节点,H、I 节点 也是兄弟节点。层次相同但父节点不同的孩子之间互称堂兄弟节点,比如图 1 中节点 F 与 节点 G,节点 J 与节点 K。
- 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:B、C;D、E、F
- 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
- 森林:由m(m>0)棵互不相交的树的集合称为森林;
除此之外,关于“树”,还有三个比较相似的概念:高度(Height)、深度(Depth)、层(Level)。它们的定义是这样的:
这三个概念的定义比较容易混淆,描述起来也比较空洞。我举个例子说明一下,你一看应该就能明白。
记这几个概念,我还有一个小窍门,就是类比“高度”“深度”“层”这几个名词在生活中的含义。
在我们的生活中,“高度”这个概念,其实就是从下往上度量,比如我们要度量第 10 层楼的高度、第 13 层楼的高度,起点都是地面。所以,树这种数据结构的高度也是一样,从最底层开始计数,并且计数的起点是 0。
“深度”这个概念在生活中是从上往下度量的,比如水中鱼的深度,是从水平面开始度量的。所以,树这种数据结构的深度也是类似的,从根结点开始度量,并且计数起点也是 0。
“层数”跟深度的计算类似,不过,计数起点是 1,也就是说根节点的位于第 1 层。
1.3 树的表示(存储结构)
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间的关系。树形结构是一对多的关系,除了树根之外,每一个节点有唯一的直接前驱(双亲),除了叶子之外,每一个节点有一个或多个直接后继(孩子)。那么如何将数据以及它们之间的逻辑关系存储起来呢?
实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法 等。我们这里就简单的了解其中最常用的孩子兄弟表示法。
每个结点包含三部分内容:指向第一个孩子节点的指针、 结点值、指向节点下一个兄弟节点的指针。 孩子兄弟表示存储结构如下图所示:
以下图为例,其孩子兄弟表示法 如图所示:
A 有3个孩子 B、C 和 D,其长子(第一个孩子) B 作为 A 的左孩子,B 的右指针存储其右兄弟 C,C的右指针存储其右兄弟 D。
B 有2个孩子 E 和 F,其长子 E 作为 B 的左孩子,E 的右指针存储其右兄弟 F。
C 有1个孩子 G,其长子 G 作为 B 的左孩子。
D 有2个孩子 H 和 I,其长子 H 作为 D 的左孩子,H 的右指针存储其右兄弟 I。
G 有1个孩子 J,其长子 J 作为 G 的左孩子。
孩子兄弟表示法的秘诀:长子当作左孩子,兄弟关系向右斜。
用代码表示:
typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一个孩子结点
struct Node* _pNextBrother; // 指向其下一个兄弟结点
DataType _data; // 结点中的数据域
};
1.4 树的实际应用
树的实际应用有很多,例如,表示文件系统的目录就是树结构
二.二叉树的概念及结构
2.1 概念
树的结构多种多样,对树的操作也各不相同,但最常用是二叉树,因为大部分树都可以转换为 二叉树。
那什么是二叉树呢?二叉树的特点是每个节点最多有两棵子树(左子树和右子树),这意味着每个节点的度(节点拥有子树的个数)都不大于2。它的两棵子树有左右之分。想象一下,人的脚是分左右的,右脚不能穿左侧的鞋,和二叉树两棵子树的左右之分是一个道理。另外,次序也是不能随意颠倒的, 这表明二叉树是一棵有序树。
这里,我们给二叉树下一个明确的定义: 二叉树是 n(n≥0)个节点的有限集合,该集合或者 为空集(即 n=0,叫做空二叉树),或者由一个根节点和两个互不相交的该根节点的 左子树 和 右子树 构成,左子树和右子树又分别是一棵二叉树。
注意:对于任意的二叉树都是由以下几种情况复合而成的:
二叉树的结构最简单,规律性最强,因此通常被作为重点讲解。
2.2 现实中的二叉树
2.3 特殊的二叉树
二叉树有一些特殊的形态在后面会经常被提到或者用到,这里我先带你认识一下满二叉树、完全二叉树以及斜树。
2.3.1 满二叉树
下图就是一棵满二叉树:
观察上图,我们先说下满二叉树有什么特点。
1. 所有的分支节点都存在左子树和右子树(非叶节点的度一定是 2)。
2. 所有的叶子都在同一层上(这也意味着叶节点只能出现在最下一层)。
3. 不存在度为非 0 和非 2 的节点。
可以看到,满二叉树看上去是很平衡的。在同样高度的二叉树当中,满二叉树一定是节点个数最多,叶子数最多的二叉树。
那要怎么去定义“满二叉树”呢?观察一下,图 5 中,第一层有 1 个节点,第二层有 2 个节 点,第三层有 4 个节点,第四层有 8 个节点,所以总共的节点数为 1+2+4+8=15 个,即 个。
由此,我们可以给出满二叉树的定义:
满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
2.3.2 完全二叉树
完全二叉树理解起来有一点难度。我们先看下图,下图就是一棵完全二叉树。
观察上图,想一想,完全二叉树有什么特点呢?
1. 叶节点都在最底下两层。
2. 最后一层的叶节点都靠左侧排列(左侧连续),并且除最后一层,其他层的节点个数都要达到最大。
3. 倒数第二层如果有叶节点,则叶节点都靠右侧排列(右侧连续)。
4. 如果节点度为 1,则该节点只有左子树,不可以只有右子树。而且最多只有一个度为 1 的 节点。
可以看到,满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满二叉树。
完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。现在,我们可以借助满二叉树的概念,给出完全二叉树的定义:
完全二叉树:对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
也就是说,完全二叉树除了最后一层,前面每一层都是满的,最后一层必须从左向右排列。也就是说,如果一个节点没有左孩子,就不可以有右孩子。
如何判断一棵二叉树是不是完全二叉树呢?如下图所示的几棵二叉树就都不是完全二叉树:
上图中,第一棵树的节点 5 缺少左子树(编号 10),第二棵树的节点 3 缺少两棵子树(编号 6、7),第三棵树的节点 5 缺少两棵子树(编号 10、11)。
所以,如何判断是否是完全二叉树,就可以按照下面的步骤去做。
在看到一棵树后,按照满二叉树的情形给该二叉树的节点进行逐层按顺序编号,如果编号出现了空缺,就不是完全二叉树,否则就是完全二叉树。
一棵满二叉树,依次把编号最大的 1 到多个节点去掉(比如去掉上面的完全二叉树图中的 15、14、13 节 点),得到的就是一棵完全二叉树。
2.3.3 斜树
所有节点都只有左子树的二叉树叫左斜树。所有节点都只有右子树的二叉树叫右斜树。这两种 树统称斜树。 斜树的特点是每一层只有一个节点,节点个数与二叉树深度相同。这种树看起来更像线性表。 下图中第 1 棵是左斜树,第 2 棵树是右斜树。
2.4 二叉树的性质和证明
在对二叉树进行编码过程中,尤其是涉及开辟多少空间保存数据的时候,往往会用到二叉树的性质。下面汇总来说,然后再证明。
性质一:若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点.
性质二:若规定根节点的层数为1,则深度为k的二叉树的最多有 个节点.
性质三:二叉树节点的总数量等于节点的总度数 +1
性质四:对任何一棵二叉树, 如果度为0的叶子节点个数为 , 度为2的节点数为 ,则叶节点的数量比有两有两棵子树的节点数量多一个,即.
性质五:若规定根节点的层数为1,具有 n 个节点的完全二叉树的高度 为或者。
注: 符号⌈X⌉表示向上取整,也就是比 X 大的最小整数。如果 X 本身是整数,那么⌈X⌉就等于本身。
符号⌊X⌋表示向下取整,也就是比 X 小的最大整数。如果 X 本身是整数,那么⌊X⌋就等于本身。
性质六:对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则编号为i的节点有:
- 若 i>0,i 位置节点的双亲序号:(i-1)/2;(若 i=0,i则是根节点的编号,无双亲节点)
- 若 2i+1<n,左孩子序号:2i+1;(若2i+1>=n,则无左孩子)
- 若 2i+2<n,右孩子序号:2i+2;(若2i+2>=n,则无右孩子)
以下是上述几个性质的证明,感兴趣的可以看一看,也不难,有助于理解记忆。
性质一的证明:
例如,一棵二叉树如下图所示。由于二叉树每个节点最多有2个孩子,第一层树根为1个节点,第二层最多为2个节点,第三层最多有4个节点,因为上一层的每个节点最多有两个孩子,因此当前层最多是上一层节点数的2倍。
使用数学归纳法证明如下。
时:只有一个根节点,。
时:假设第 层有个节点,而第 i 层节点数最多是第 层的2倍,即第 i 层节点数最多有
性质二的证明:
如果深度为的二叉树,每一层都达到最大节点数,也就是满二叉树,把每层的节点数都加起来就是整棵二叉树的最大节点数。
性质三的证明:
观察一棵二叉树不难发现,除根节点外,每个节点头上都有一个分支 / 树枝(每个节点都有一 个父节点),那么一棵二叉树节点总数量其实就是这些分支的总数量 +1,之所以 +1,是因为根节点头上没有分支。一棵二叉树节点的总度数,其实就是每个节点头上分支的总数量,所以得出性质三的结论:二叉树节点的总数量 = 节点的总度数 + 1。 下面性质四的部分证明中也有性质三的证明,可以继续往下读。
性质四的证明:
二叉树中的节点度数不超过2,因此一共有3种节点,即度为0、度为1、度为2。设二叉树总的节点数为,度为0的节点数为,度为1的节点数为,度为2的节点数为,总节点数等于3种节点数之和,即。 而总节点数又等于“分支数”,即 。为什么呢?如下面左图所示,从下向上看每一个节点对应一个分支,只有树根没有对应分支,因此总的节点数为“分支数” 。
而分支数怎么计算呢?
如下面右图所示,从上向下看,每个度为2的节点产生2个分支,度为1的节点产生1个分支,度为0的节点没有分支,因此分支数,则 。而前面已经得到 ,两式联合得:.
性质五的证明:
假设完全二叉树的深度为k,那么除了最后一层外,前k-1层都是满的,最后一层最少有一个节点,如下图所示。最后一层最多也可以充满节点,即个(此时完全二叉树就是满二叉树)。所以一个具有n个节点的完全二叉树,它的节点数量的范围为:,右边放大后,,同时取对数,,所以。其中, 表示取下限,表示小于的最大整数,如。
这里截取另一个证明方式,跟上述方法基本类似。
性质六无需证明,很简单,可以自己画图算算。
三.小结
本篇的内容,我们尝试描述了树形结构,也给出了许多和它相关的基本概念,其中比较重要的概念 是树根、子树、节点的度,叶节点、子节点、父节点、兄弟节点、树的高度这几个部分。之后引入了二叉树的概念。可以说,二叉树在树形结构中最常用,所以本篇内容以及后面文章还会用很多篇幅讲述二叉树相关的知识。这一篇文章将重点放在了认识各种形态的二叉树上,比如理解满二叉树、完全二叉树、斜树的概念。
然后,则是二叉树性质的学习。也许你会觉得这些性质离实际应用太过遥远,但其实它对于后面编写程序时决定内存空间分配多少、二叉树的高度和节点数量如何计算以及如何寻找某个节 点的父或子节点等都具有重要的指导意义,这一点在后续编写代码时你会越来越清楚。
当然,这些性质不要死记硬背,理解性记忆即可,后面的课节中编程时会偶尔用到二叉树的性 质。如果你忘记了这些性质,随时复习一下本节内容也就可以了。
最后,如果您觉得本篇文章对你起到了一点点的帮助,希望您能点个赞,或者收藏一下,鼓励下我,这对我很重要❤️。
点击本篇文章最开始介绍栏目的数据结构与算法专栏链接,继续学习阅读下篇有关二叉树的内容吧,下一篇将会根据二叉树的存储结构和性质,对二叉树进行编码实战。✨✨