欢迎来到 CILMY23 的博客
🏆本篇主题为:树和堆的奇妙世界:数据结构的无尽可能
🏆个人主页:CILMY23-CSDN博客
🏆系列专栏:Python | C++ | C语言 | 数据结构与算法 | 贪心算法 | Linux | 算法专题 | 代码训练营
🏆感谢观看,支持的可以给个一键三连,点赞收藏+评论。如果你觉得有帮助,还可以点点关注
前言:
hello,大家好,这里是CILMY23的频道,今天我们接触的是树,这是数据结构中的树,那接下来就跟随我的视角来看看吧。
个人分享:每个人的内心都是一片海洋,每一路人都如此。每个人都是思想、见解和情感的宇宙。
目录
1.树的概念及结构
树的概念
我们之前所接触的数据结构的都是线性数据结构,这里要分清楚逻辑结构和物理结构,逻辑结构是我们想象出来的,而物理结构是内存中实在的逻辑结构。
哪些数据结构是线性的呢?
线性表,链表,栈和队列都是线性的,对逻辑结构的描述。逻辑结构和物理结构可能是一致的,也可能不是一致的。
我们在讲链表的时候,都是画箭头的,箭头是我们想象出来的,它呈现出一条线性,实际上有没有箭头?没有,实际上是上一个节点存下一个的地址,这些数据是从堆上开的,它的物理结构可能是这样的:
而非线性的数据结构,从逻辑结构上就不在是一个一个挨着的,更像是一个倒挂的树。
是不是很像呢?
所以树的概念也有了。
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
- 有一个特殊的结点,称为根结点,根节点没有前驱结点
- 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
- 因此,树是递归定义的。
树的相关概念
接下来我们就要讲述一些关于树的相关概念:
当然,这里过于繁琐,我也提供一种我的解读版本:
节点的度:就是这个节点有几个子节点。
叶节点或终端节点:度为0
分支节点或非终端节点:度不为0.
父节点或双亲节点:若一个节点有子节点,则这个节点是子节点的父节点。
子节点或者孩子节点:不必多说。
兄弟节点:具有相同父节点(亲兄弟)
树的度:最大的度。
节点的层次:从根节点开始,根节点算第一层,依次类推。
从0开始,层次为3
从1开始,层次为4.建议是这种从1开始。
树的高度或深度:树中结点的最大层次。
堂兄弟节点:双方父节点在同一层。
结点的祖先:根节点是所有结点的祖先。
AEJ都是Q的祖先。
结点的子孙:所有结点是根结点的子孙。
森林:由m棵互不相交的树构成的(m>0)
总结:
所以一般来说一棵树有三个部分构成:根节点,分支节点,叶节点。
一个节点有可能是子节点,也有可能同时是父节点。其实树跟人类亲缘关系挂钩。就像父亲可能是有很多个孩子,但是你的亲生父亲就一个。
树的另外两种形态:空树,根节点
我们认为空树的高度是0,而有根节点的高度是1.这样能清楚的区分空树和不是空树的情况。
树与非树
为什么说树是递归定义的呢?其实任何一棵树都会被定义成根和子树。 (空树没有节点数,或者说节点数是0).
那什么时候拆解到头呢?
当我们拆解到叶子结点的时候就结束了。所以树和二叉树一般用递归解决,但不是只能用递归解决。
所以树的特点如下:
- 子树是不相交的:
- 除了根结点外,每个结点有且仅有一个父结点:
- 一棵N个结点的树有N-1条边。
而非树呢,就不满足上述条件:
图
我们也把这种非树叫做图,这种树更为复杂,表示起来更繁琐,我们之后才会接触。
2.树的表示
树应该如何表示?它的一个父节点可能有很多个子节点。每一个子节点对应一个父节点。
其实树的存储表示有很多种结构。
第一种:用指针数组进行表示。
#define N 6
struct TreeNode
{
int val;
struct TreeNode* childArr[N];
};
用指针数组表示可能会浪费空间,并不是所有的节点度都为6,这些节点可能度为2,为3,所以会造成空间的浪费。
第二种:用顺序表进行表示。
顺序表相对于数组而言,更加灵活,可以动态开辟空间。
因为我现在学过C++中的动态数组vector,我们能知道,struct TreeNode* 这种算它的对象类型。
也就是顺序表当中开辟了每一个 struct TreeNode*的对象。 所以用C++更好表示,vector就是顺序表。但是也有其他方法来表示。
第三种:用“左孩子右兄弟”进行表示。
这种思维方式非常巧妙,
struct TreeNode
{
int val;
struct TreeNode* LeftChild;
struct TreeNode* RightBrother;
};
这是我们上图中,用来表示树的逻辑结构的情况,而我们在实际表示的时候不这样了。
如图所示:
这种表示方法真的是非常的巧妙,从根节点开始,左孩子就指向最左边的一排,无论你的兄弟节点有多少个,都可以通过右兄弟的指针,往下指向,直到没有为止。 这样无论度是多少,都能玩。
当我们想遍历第二层的子节点的时候,直接让child为指向左节点的地方就好。
struct TreeNode* child = A->LeftChild;
while (child)
{
printf("%d", child->val);
child = child->RightBrother;
}
上述代码只能遍历第二层的,如果我们想一直往下遍历就要稍微改变一下。
struct TreeNode* child = A->LeftChild;
while (child)
{
struct TreeNode* cur = child;
while (cur)
{
printf("%d ", cur->val);
cur = cur->RightBrother;
}
child = child->LeftChild;
printf("\n");
}
这样就能遍历每一层的子节点了。
3.树的应用
我们在Linux的番外,文件中讲解过,Linux的文件系统是一棵树,那Windows系统呢,其实是个森林,每个磁盘都对应一棵树。(详情看链接)
所以ls 其实就是遍历一遍子节点,cd 就是进入子树的根节点。
4.二叉树的概念及结构
一棵二叉树是结点的一个有限集合,该集合:
1. 或者为空
2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成
二叉树在实践当中用的很多。二叉树就是最多两个孩子。
二叉树等不等价于度为2的树?
:不等价,二叉树可能是度为2的树,比如空树,只有根节点。二叉树是最大度为2,并不是说度一定为2.
度为2可以认为是二叉树吗?
:可以,二叉树是一个特殊的树,最大的度为2,并没有说一定是2.
所以二叉树的特点如下:
- 二叉树不存在度大于2的结点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
二叉树又有以下情况:
完全二叉树 && 满二叉树
满二叉树:
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是
,则它就是满二叉树。
完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
说白了满二叉树就是每一层节点数都达到最大 (除了叶子节点,每一个节点的度为2),而完全二叉树就是前h-1层都是满的(每一个节点的度为2),第h层可能满,可能不满,但是它的节点必须从左到右连续。
满二叉树的特点
满二叉树某一层节点多少?
:
这是一棵满二叉树,第一层也就是根节点,总共有一个节点,第二层,总共有两个节点,第三层,总共有4个节点,依次类推, 第h层,就有 个节点。
满二叉树的第h层的总节点多少?
:
推理过程如下,其实是用等比数列来做的。
这是针对第n层的情况:
满二叉树的总节点数为N,那么它有多少层?
:
推理过程如下:
完全二叉树的特点
完全二叉树的高度为h,问结点数量
:[
,
]
因为前h-1层可以看作是一棵满二叉树,所以它的结点数量是 ,到第h层时最少有一个,所以加一,所以最少的情况是
。
如果第h层是满的,那么可以看作是一棵满二叉树,故结点是。
5.二叉树的存储结构
二叉树的存储分顺序存储和链式存储。
顺序存储
顺序结构存储就是使用数组来存储,一般只适合表示满二叉树或者完全二叉树,因为不是完全二叉树或者满二叉树会有空间的浪费。它是一层一层存到数组当中去,而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理结构上是一个数组,在逻辑结构上是一颗二叉树。 二叉树没必要用左孩子右兄弟。直接定义左孩子和右孩子即可。
如果我们使用数组存储便会发现,左孩子在奇数位置,右孩子在偶数位置。这也是为什么用数组存储的原因。因为有规律关系,能很快确认父子关系。
如图所示:
规律关系:
左孩子 LeftChild = parent * 2 + 1
右孩子 RightChild = parent * 2 + 2
父结点 parent = (child - 1) / 2
链式存储
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。
非完全二叉树就不适合二叉树存储,只适合我们的链式结构,因为在顺序存储中,如果是空结点,我们得把对应的位置空出来,这样会存在空间浪费。
6.堆的概念
如果有一个关键码的集合K = {
,
,
,……,
},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:
<=
且
<=
(
>=
且
>=
) ,i = 0,1, 2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
说白了,堆是一般是把数组数据看作一个完全二叉树。小堆(小根堆):任意一个父亲都小于等于孩子,大堆(大根堆):任意一个父亲都大于等于孩子
堆的练习:
一个有序数组能不能看作是一个堆?
:一定是,是小堆或者大堆。
堆是不是一定有序?
:不是
数据结构的堆和内存中的堆
:它两只是同名
堆的意义是什么?
:1.堆排序,它的时间复杂度能到O(
)
:2.TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
总结:
1.空树的高度是0,而有根节点的高度是1
2.树是一种非线性的数据结构,它的特点如下:
- 子树是不相交的:
- 除了根结点外,每个结点有且仅有一个父结点:
- 一棵N个结点的树有N-1条边。
- 树的表示可以用“左孩子右兄弟”来表示
3.二叉树是一种特殊的树,它的特点如下:
- 二叉树不存在度大于2的结点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
4.满二叉树 && 完全二叉树 的性质
满二叉树某一层节点多少?
:
满二叉树的第h层的总节点多少?
:
满二叉树的总节点数为N,那么它有多少层?
:
完全二叉树的高度为h,问结点数量
:[
,
]
5.堆是一般是把数组数据看作一个完全二叉树。小堆(小根堆):任意一个父亲都小于等于孩子,大堆(大根堆):任意一个父亲都大于等于孩子。
6. 顺序存储适合满二叉树或者完全二叉树,而非完全二叉树适合用链式存储表示。
🛎️感谢各位同伴的支持,本期数据结构的树专题就讲解到这啦,下期我们将进入堆的实现,如果你觉得写的不错的话,可以给个一键三连,点赞,收藏+评论,可以的话还希望点点关注,若有不足,欢迎各位在评论区讨论。