欢迎来到Harper.Lee的学习世界!
博主主页传送门:Harper.Lee的博客主页
想要一起进步的uu可以来后台找我哦!
一、树的概念及其结构
1.1 树的概念+亲缘关系
树是一种非线性的数据结构,它是由n(n>=0)个有限节点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
要点:
1. 有一个特殊的节点,称为根节点,根节点没有前驱节点。
2. 除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。(每个孩子只能有一个父亲,一个父亲可以有多个孩子)。
3. 因此,树是递归定义的。
任何一棵树都包含了根和N棵子树(N>=0),子树又由新的根和子树组成;N=0时,该树被称为空树。就像递归一样,将大问题逐步拆解成一个个不可再拆解的小问题。(很形象的描述就是,递归其实是套娃🪆)
注意:在树形结构中,子树之间不能有交集,否则就不是树结构了!!!(子树之间相交叫做图)
1.2 树的相关名词
树是由根和子树构成的,也可以说是分支节点和叶子节点构成的。我们可以根据树+人类亲缘关系来定义解读树的相关名词。
名词 | 意义 | 上图中表示 |
节点的度 | 一个节点含有的子树的个数 | A节点的度为6 |
叶节点或终端节点 | 度为0的节点 | B、C、H、I、P、Q、K、L、M、N节点称为叶节点 |
非终端节点或分支节点 | 度不为0的节点 | A、D、E、F、G、J节点为分支结点 |
双亲节点或父节点 | 若一个节点 含有子节点,则这个节点称为其子节点的父节点 | A是B的父节点 |
孩子节点或子节点 | 一个节点含有的子树的根节点称为该节点的节点 | B是A的孩子节点 |
兄弟节点 | 具有相同父节点的节点(相当于是亲兄弟)互称为兄弟节点 | B、C是兄弟节点 |
树的度 | 一棵树中,最大的节点的度称为树的度 | 因为A节点的度为最大的度,所以树的度为6 |
节点的层次(Level) | 从根开始定义起,根为第1层,根的孩子为第2层,以此类推,就相当于楼层;树中节点的最大层次称为树的深度(Depth)或高度。 | 上图节点的层次为4(相当于4层楼) |
树的高度或深度 | 树中节点的最大层次,相当于最大楼层数 | 上图中树的最大高度为4 |
堂兄弟节点 | 双亲在同一层的节点互为堂兄弟(相当于不是同一个双亲) | 上图H、I互为堂兄弟节点 |
节点的祖先 | 从根到该节点所经分支上的所有节点(简而言之,祖先都是一条线上的蚂蚱🦗) | 上图中A是所有节点的祖先 |
子孙 | 以某结点为根的子树中任一节点都称为该节点的子孙 | 上图中所有节点都是A的子孙 |
森林 | 由m(m>0)棵互不相交的树的集合称为森林(森林是一群树) | 上图是单独一棵树,不能构成森林 |
深入讨论:
a. 为什么根节点的层次是从第1层开始定义的,而非第0层?
现有一棵树没有子树,那么如果从1开始定义,那么根节点的高度为0;如果从0开始定义,那么根节点的高度为-1。
b. 那么为什么C语言中的数组下标从0开始呢?
因为数组的下标从0开始,便于计算。数组名是数组首元素的地址。例如arr[i]等价于*(arr+i)。而如果数组的下标从1开始,那么arr[i]代表第i个元素,而*(arr+i)代表第i+1个元素,二者不再等价。
1.3 树的表示方法
树的结构相比较于以往的其他结构就比较复杂了,要存储起来表示就比较有难度,不仅要保存值域,也要保存节点和节点之间的关系。实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法等。
1.3.1 已知节点的度
如果已知节点的度,那么就可以根据这个度来确定树的结构体中需要孩子指针的数量。
//明确树的高度为N(树的度:一棵树中, 含有的子树个数最大的节点称为树的度)
typedef int DataType;
struct TreeNode
{
DataType val;
struct TreeNode* subs[N];//定义了一个指针数组
};
1.3.2 未知节点的度
如果不知道节点的度,那么我们就需要另寻他法来定义树结构。下面就是常用的定义表示法。
(1)双亲表示法
双亲表示法的基本思想:用一维数组来存储树的各个节点(一般按层序存储),数组中的一个元素对应树中的一个节点,包括节点的数据信息以及该节点的双亲在数组中的下标。
//双亲表示法
typedef int DataType;
struct PNode
{
DataType data; //数据域
int parent; //指针域,双亲在数组中的下标(即用整型来表示父亲节点的位置)
};
树的双亲表示法实质上是一个静态链表。当算法中需要在树结构中频繁地查找某节点的父节点时,使用双亲表示法最合适。当频繁地访问节点的孩子节点时,双亲表示法就很麻烦,采用孩子表示法就很简单。
(2)孩子链表表示法
孩子链表的表示方法:链表中的每个节点包括一个数据域和多个指针域,每个指针域指向该节点的一个孩子节点。
其中,data:数据域,存放该节点的数据信息;
child1~childn:指针域,指向该节点的孩子。
孩子链表的基本思想:把每个节点的孩子排列起来,看成是一个线性表,且以单链表存储,则n个节点共有n个孩子链表。这n个单链表共有 n个头指针,一起组成一个线性表,为了便于进行查找采用顺序存储。最后,将存放n个头指针的数组和存放n个节点的数组结合起来,构成了孩子链表的表头数组。
使用孩子表示法存储的树结构,正好和双亲表示法相反适用于查找某节点的孩子节点,不适于查找其父节点 。可以将两种方法合二为一。(博客园--gonghr)
(3)双亲孩子表示法
(4)左孩子右兄弟表示法
左孩子右兄弟表示法是是我们经常用的一种方法。无论一个父亲节点有多少个孩子,leftChild都指向左边开始的第一个孩子节点。rightBrother指向同一层的兄弟,而且这个兄弟是同样的父母(相当于leftChild是双亲带大的第一个孩子即老大,剩下的孩子老二老三等就有老大带大)关系图如下:
//左孩子右兄弟表示法
typedef int DataType;;
struct TreeNode
{
DataType val;//节点中的数据域
struct TreeNode* firstChild;//左边孩子指针(第一个孩子节点)
struct TreeNode* pNextBrother;//右边兄弟指针(指向下一个兄弟节点)
};
既然已经定义好了树的结构,那现在应该如何通过左孩子右兄弟找到所有的孩子节点?示例代码如下:
//通过左孩子右兄弟找到所有的孩子节点:
struct TreeNode* parent;//定义树的结构体指针
struct TreeNode* cur = parent->leftChild;
while (cur)
{
//……
cur = cur->rightBrother;
}
1.4 树的相关应用
(1)文件系统中的目录树结构就是经典的树结构。
我们打开磁盘,在底层就是通过磁盘的孩子指针找到第一个孩子,再通过第一个孩子的兄弟指针开始逐个遍历后面的兄弟节点,才能把整个目录给列举出来。
如果我们新建一个文件夹,就是让该文件目录下的兄弟节点指向NULL的文件指向这个新建文件,然后新建文件的兄弟指针指向NULL,当然这个也要看情况,有时候文件排序的方式也是不同的
(2)Linux树状目录结构
二、二叉树的概念及其结构
在所有的树的相关结构中,二叉树是我们经常用的一种结构。
2.1 二叉树概念
一棵二叉树是节点的一个有限集合,该集合的特点是:1. 要么为空;2. 要么由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
二叉树的特点:a. 二叉树的度最大为2(相当于对其进行了计划生育,最多生育2个孩子);b. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树。
注意:对于任意的二叉树都是由以下几种情况复合而成的:
2.2 特殊的二叉树
1. 满二叉树:一个二叉树,如果每一个层的节点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且节点总数是2k-1 ,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。(简言之,完全二叉树的最后一层不是满的)
2.3 二叉树的性质
1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2(i-1)个结点。
2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 2h-1。
(ps:20层满二叉树,节点数为100W+;30层满二叉树,节点数为10亿+)
3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为n2 ,则有n0 =n2 +1。
4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h=log2(n+1)。
5. 对于具有 n 个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为 i 的结点有:
a. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点;
b. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子;
c. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子。
2.4 二叉树的存储结构
二叉树一般可以使用两种结构存储,一种是顺序结构存储,一种是链式结构存储。
2.4.1 顺序存储
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。
二叉树顺序存储在物理上是一个数组,在逻辑上是一棵二叉树。分析过程如下:
一般来说,顺序存储只适用于完全二叉树(满二叉树是特殊的完全二叉树)!
2.4.2 链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链。
喜欢的uu三连支持一下嗷!