数据结构自学笔记五、树(上)

本文是数据结构自学笔记的第五篇,重点探讨树的基本概念,包括树的定义、结点分类、结点间的关系以及树的其他特性。文章通过生活中的例子解释树的抽象概念,并介绍了树的存储结构,如双亲表示法、孩子表示法和孩子兄弟表示法。同时,文章对二叉树进行了初步介绍,包括二叉树的定义、特点和特殊类型。
摘要由CSDN通过智能技术生成

不知不觉已经写了五篇博客了,看来这种把笔记写成博客的学习方式真的挺不错的,因为想到可能会有人会看到,那些自学本来不愿意敲的代码也不嫌麻烦敲了一遍,确实效果更好了。
不多说了,进入正题:树。
树在我们生活中的应用很多,最常见的就是建立目录。比如你用电脑办公,一天下来堆积了很多文件,你肯定不希望这些文件堆积在你的电脑桌面上挡掉你那好看的壁纸。为此,一个很好的做法就是按照一定的逻辑层次建立文件夹,把文件放入适当的位置。在你需要使用这些文件的时候,你再一层层地去找。这种数据结构使得我们可以比较方便地去访问和存储我们需要的数据。
由于树的内容比较多,我会用两篇博客来记录自己学到的内容。本篇为上篇,主要包含树的基本定义与性质。功能实现会放在下篇讲。

树的定义

树是n个结点的有限集。n=0时称为空树;n>0时为非空树。
在任意一棵非空树中:有且只有一个特定的结点,称为(root);当树不只有一个结点时,其余节点可分为m个互不相交的有限集,其中每一个集合本身又是一棵树,并且称为的子树。

特别提醒:
有且只有一个:只要树非空,就必有根结点且只有一个
子树:光提子树是没有意义的,一定要指明是哪个结点的子树
互不相交:一个结点子树的数量没有限制,但是子树之间一定是互不相交的。可以这么理解,双亲可以有多个孩子,但是孩子在血缘上只能有一对双亲。(这里咱就别纠结家庭重组问题了)

结点的分类

度的概念

一个结点拥有子树的数量称为该结点的
树的度是树内各结点的最大值

叶结点

度为0的结点,也称终端结点
度不为0的节点称为非终端结点分支结点,也可称为内部结点(除根结点)

结点间的关系

孩子与双亲

结点的 子树的 根称为该结点的孩子。相应地,该结点称为孩子的双亲。(这种命名也体现了结点的子树不相交的特点)

兄弟

同一个双亲的孩子间互称兄弟(某种意义上的常识hhh)

祖先与子孙

结点的祖先是从根到该结点所经分支上的所有结点。
以某结点为根的子树中的任意结点都称为该结点的子孙

这里要特别注意一点!确实结点间不少关系都可以通过现实中的家族关系来类比,但是祖先这个概念和长辈是不同的概念。在生活中我们会称我们的姨父姨母为“长辈”,但是他们不是你的“祖先”,因为你没有继承他们的基因。
所以,在树中,可以通过由层序低到层序高的线连接起来的结点才可以称祖先和子孙。

树的其他概念

结点的层次

从根开始定义,根为第一层,根的孩子为第二层,以此类推

堂兄弟

双亲在同一层的结点间互称堂兄弟

树的深度(高度)

树中结点的最大层次称为树的深度或高度。

有序树、无序树

如果将树中结点的各子树看成从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。

森林

若干棵互不相交的树的集合称为森林

以上就是树的基本概念。其实我感觉这个数据结构也是从生活中抽象来的,和生活中很多概念蛮贴切的。如果能类比这些概念当然有助于记忆,但是也不可完全照搬,要注意树独特的地方。
接下来介绍树的存储结构。

树的存储结构

树其实也是一种特殊的线…诶,打顺手了,这次应该要反过来了:线性表是一种结构特殊的树!假设每个结点只有一个孩子,那这不就是一个线性表么!
但是在这里,我们要讨论的是一棵平凡的树的存储结构,就不能像之前讲线性表那样,简单粗暴地把存储结构分为顺序存储和随机存储两种了。
这次,我们按照存储的思想方法,把存储结构分为:双亲表示法、孩子表示法、孩子兄弟表示法

双亲表示法

这是一种顺序存储结构的存储方式,通过建立结构体数组来存放树。
其想法为:定义一个结构体node,数据域存每个结点的数据,指针域存一个整型数,用来记录双亲的数组下标。其中根结点的指针域设为-1。

struct node{
	int data;//存数据
	int parent;//存双亲的数组下标
};

int main()
{
	node Ptree[20];//声明一个结构体数组Ptree
}

什么?你问我为什么代码到这里就停了?因为这篇博客重视的是基本内容,功能的实现(比如生成一棵树)要放到下一篇博客才讲了。
(其实就是自己还没学到,还不会(x )
然后将树的各个结点按从左到右、从上到下的顺序放入数组中,每个结点的指针域存双亲结点的数组下标。这样就可以轻松的访问自己的双亲结点的数据啦!时间复杂度为O(1)。
但是呢,如果想要找到孩子结点,还是要遍历整个数组才行,时间复杂度为O(n)。有没有什么办法能解决?
可能有人会说,既然指针域可以存双亲的下标,为什么不能存孩子的下标呢?如果没有孩子就存-1呗!很好!这是一个很好的解决方案!
那么同理,如果你设计的树使用的场合更加注重结点兄弟间的关系,你也可以添加一个右兄弟域,存放结点的右兄弟的下标(如果有)。

这也告诉我们一个道理,数据结构的设计是灵活、需要变通的,而不是死板、固定的。

孩子表示法

孩子表示法属于顺序存储结构和随机存储结构共同使用。
其思想方法为:设计一个结构体node,和一个结构体ptr。前者用来表示结点,数据域存结点数据,指针域存一个ptr*类型的指针(如果结点没有孩子则为NULL)。ptr表示用来存放孩子的下标的结构体,数据域存放相应孩子的下标,指针域存放指向存放该孩子右兄弟的下标的ptr结构体的指针。
每个节点依然是通过顺序存储结构存在一个结构体数组里。

这个数据结构在设计上非常重视孩子,通过双亲可以知道每个孩子的位置。但是相反,孩子们却不知道双亲的位置。像极了当今社会的家庭关系,双亲总是牵挂着孩子,而孩子们却忘记去关心双亲。

我们当然不能做这样的人!因此,我们把孩子表示法和上面的双亲表示法结合一下,让node结构体的指针域再多一个int型变量,存放每个结点双亲的数组下标。双亲和孩子互相牵挂,多好!改进后这种和谐的表示法被称为双亲孩子表示法

孩子兄弟表示法

上述两种表示法,分别关照了双亲和孩子,但是似乎忽视了兄弟之间的关系。我们接下来要讲的这种表示法叫做孩子兄弟表示法。这种表示法有着极为重要的作用!并且形成了一棵极其特殊且重要的树——二叉树。

这种表示法采用的是一种随机存储结构。思想方法大致是这样的。
定义结构体node,这个node有一个数据域、一个左指针和一个右指针。左指针命名为firstchild,指向自己的长子(最左边的孩子);右指针命名为rightbrother,指向自己右边的兄弟。

这种表示法的好处在于,每个结点只会有至多两个孩子,或者说,每个结点最多有两个分叉。像这种结构的树,我们把它称为二叉树。二叉树有很多好用的性质,之后我们会介绍到它。

当然,如果你想要快速查找到结点的双亲,也可以再增加一个指针域专门指向双亲。

二叉树

二叉树的名气是如此之大,以至于我在刚学C语言的时候就听过它的名字。只不过一直没有见过它本“树”,也不清楚它的作用与性质。

不过所幸,我现在知道了。久仰大名了,二叉树!

二叉树的定义

我们只需要在树的定义上做进一步约束,即可得到二叉树的定义。

二叉树是n个节点的有限集合。当n=0时,该集合为空集,称为空二叉树;当n大于0时,该二叉树由根结点和两棵不相交的二叉树(称为左子树和右子树)组成。(子二叉树也可为空树)

树和二叉树的定义都采用的是递归定义的方式定义的。如果你已经理解了什么是树,那么通俗的来讲,二叉树就是每个结点至多有两个分叉(孩子)的树,所以叫二叉树。

二叉树的特点

1.最多有二个子树

2.左子树和右子树是有顺序的,不能颠倒。即使树中某个结点只有一棵子树,也要区分它是左子树还是右子树。(这或许与结点的指针域有关,毕竟一个左指针一个右指针,不能乱指呀)

特殊的二叉树

1.斜树
所有结点都只有左子树的二叉树叫左斜树,反之叫右斜树。斜树可以看做线性表,实际上线性表就是一种特殊结构的树。

2.满二叉树
所有分支结点都存在左子树和右子树,且所有叶结点都在同一层上的树叫做满二叉树。

3.完全二叉树
对一棵具有n个节点的二叉树按层序编号,如果其结点的编号与满二叉树中结点的编号完全一致,则称之为完全二叉树。
或者换一种理解,二叉树中所有的结点严格按照从左到右、从上到下的顺序排布,则称之为完全二叉树。
满二叉树一定是完全二叉树,但反之不成立。

二叉树的性质

之前提到了,二叉树有一些非常好的性质,让我们可以使用它完成一些事情。我们现在来介绍二叉树的性质。

1.在二叉树的第i层上至多有2^(i-1)个结点
考虑满二叉树的情况,相当于每一层的结点数构成了等比数列。公比为2

2.深度为k的二叉树至多有2^k -1个结点
理由同上,等比数列求和

3.对于任何一棵二叉树,如果其终端结点(度为0)数为n0,度为2的结点数为n2,则n0=n2+1
我们暂且把连接两个结点之间的东西称为连接线,算一算一棵树有多少根连接线
如果从接收的角度思考,除了根结点外,每个结点都有一根连接线连接它,所以连接线的数量为n1+n2+n0-1
如果从发出的角度思考,发出的总连接线数为2*n2+n1。两个式子取等号,移项,得证n0=n2+1

4.具有n个结点的完全二叉树的深度为(int)log 2 n+1
对于一个深度为k的完全二叉树,它的结点数肯定要大于深度为k-1的满二叉树,即n≥2^(k-1);它的结点数肯定要小于等于深度为k的满二叉树,即n≤2^k - 1。取证即可得证。
5.如果对一棵有n个结点的完全二叉树的结点按层序编号,对任意结点i,有:
(1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点(int)i/2
(2)如果2i>n,则结点i无左孩子;否则其左孩子是结点2i
(3)如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1

二叉树的存储结构

顺序存储结构

我们想想,对于一棵完全二叉树而言,其按层序编号,编号是不是连续的?
答案是肯定的,这也就意味着,如果采用顺序存储结构,完全二叉树的这种非常好的特性使得空间完全不会被浪费!
但如果是一棵普通的树呢?我们同样可以用类似的方法先将其补全为完全二叉树,然后建立顺序存储结构,不过之前补上的地方在这里应当设为NULL。
不过,如果这是一棵右斜树,那么就会造成极大的空间浪费。因此,只有完全二叉树才比较适合使用顺序存储结构。

二叉链表

其实在讲孩子兄弟表示法时,我们就提到了这个方法。构建节点结构体,设置左指针和右指针,分别指向左孩子和右孩子。这种方法非常形象的表示了二叉树的结构。这样的链表我们称为二叉链表。
当然,我们也可以在指针域加上第三个指针,指向双亲。这样的链表称为三叉链表。

写在结尾的话

关于树的内容已经学了一半了,明天我们会学二叉树的功能的代码实现,有些小期待。

今天是教师节,祝愿我的恩师们,以及所有的善良的老师们节日快乐~

最后还是要提一嘴,看我的博客也就图一乐,真要学知识还得多看其他大牛的博客或者去买一本教材。如果你发现我的博客中有任何疏漏的地方,请在评论区提出,十分感谢~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值