重学数据结构之第六章-树

1、树的定义

树(Tree)是n(n>=0)个结点的有限集。当n=0时,称为空树
在任意一棵非空树种:
(1)、有且仅有一个特定的称为根的结点(Root)
(2)、当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、…、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
如下图所示:

在这里插入图片描述

本书中还是少了一些有关红黑树的概念。
树有很多概念和定义需要我们去理解记忆。

1.1、树结点分类

树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树称为结点的度(Degree)。度为0的结点称为叶结点(Leaf)或终端结点

度不为0的结点称为非终端结点分支结点。除根结点之外,分支结点也称为内部结点。

树的度是树内各结点的度的最大值。如下图所示,这棵树结点的度的最大值是结点D的度,为3,所以这颗树的度是3。

在这里插入图片描述

1.2、树的结点之间的关系

结点的子树的根称为该结点的孩子(Child),该结点称为孩子的双亲(Parent)。
同一个双亲的孩子之间互称为兄弟(Sibling)。

结点的祖先是从根到该结点所经分支上的所有结点。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。如下图所示:

在这里插入图片描述

1.3、树的其他概念

结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。

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

树中结点的最大层次称为树的深度(Depth)或高度

如下图所示:

在这里插入图片描述
如果将该树中的各子树看成从左到右是有秩序的 ,不能互换的,则称该树为有序树,否则称为无序树。

森林(Forest)是m(m>=0)棵互不相交的树的集合。

2、树的存储结构

不能使用简单的顺序存储结构和链式存储结构进行概况了 ,树是一种比较特殊的结构。
本节后面的表示法,都是基于这张图的:

在这里插入图片描述

2.1、双亲表示法

树这种结构除了根节点之外,其余每个结点,它不一定有孩子,但是一定有且仅一个双亲。
一个结点:包含一个数据域data,存储数据元素,一个parent指针域,存储该结点的双亲在数组中的下标。

如下图所示:
在这里插入图片描述

2.2、孩子表示法

由于树中每个结点可能有多棵子树,可以考虑用多重链表,即每个结点有多个指针域,其中每个指针指向一棵树的根节点,我们把这种方法叫作多重链表表示法。
下面有两种方案:
方案1:
在这里插入图片描述
上面这种方法存在很多空间上的浪费。
方案2:
在这里插入图片描述这种方法克服了浪费空间的缺点,对空间利用率很高,但是由于各个结点的链表是不相同的结构,加上要维护结点的度的数值,在运算上就会带来时间上的损耗。

最终我们找到了解决方案,也就是孩子表示法
具体操作是,把每个结点的孩子结点排列起来,以单链表的作为存储结构,则n个结点有n个孩子的链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组。如下图所示:

在这里插入图片描述
这样的结构对于我们要查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表即可,对于遍历整棵树也是很方便的,对头结点的一维数组循环即可。

2.3、孩子兄弟表示法

刚才我们从双亲和孩子的角度去探讨了数据结构,我们现在以兄弟的角度去考虑。

任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的,因此我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。

所以我们定义结点结构如下:

在这里插入图片描述
实现示意图如下:

在这里插入图片描述
引出二叉树

3、二叉树的定义

二叉树(Binary Tree)是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根节点或两棵互不相交的、分别称为根节点的左子树右子树组成。

来一张认识一下:

在这里插入图片描述

是一种很特殊的树结构。

3.1、二叉树的特点

包括三个特点和五种形态:
三个特点:
1、每个结点最多只有两棵子树,所以二叉树中不存在度大于2的结点;
2、左子树和右子树是由顺序的,次序不能任意颠倒;
3、即使树种某结点只有一棵子树,也要区分它是左子树和右子树。
五种形态:
1、空二叉树;
2、只有一个根结点;
3、根结点只有左子树;
4、根节点只有右子树;
5、根结点既有左子树又有右子树。

一张图认识一下3个结点组成的5种不同的二叉树:
在这里插入图片描述

3.2、特殊的二叉树

从树-二叉树-特殊二叉树这个顺序都是对于树延伸到细枝末节的结果。

3.2.1、斜树

所有的结点都只有左子树的二叉树叫左斜树

所有的结点都只有右子树的二叉树叫右斜树

3.2.2、满二叉树

在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
如下图所示:
在这里插入图片描述

3.2.3、完全二叉树

对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
如下图所示:
在这里插入图片描述

“完全”和“满”的差异,满二叉树一定是一个完全二叉树,但完全二叉树不一定是满的。

4、二叉树的五大性质

1、在二叉树的第i层上至多有 2^(i-1) (2的i-1次方)个结点(i>=1)。
2、深度为k的二叉树至多有2^k-1(2的k次方-1)个结点(i>=1)。
3、对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
4、具有n个结点的完全二叉树的深度为└log2n┘+1(└X┘表示不大于x的最大整数)。
5、如果对一棵有n个结点的完全二叉树(其深度为└log2n┘+1)的结点按层序号编号(从第一层到第└log2n┘+1层,每层从左到右),对任一结点i(1<=i<=n)有:
(1)、如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点└i/2┘;
(2)、如果2i>n,则结点i无左孩子(结点i为叶子结点),否则其左孩子是结点2i;
(3)、如果2i+1>n,则结点i无右孩子,否则其右孩子是结点2i+1。

5、二叉树的存储结构

5.1、二叉树的顺序存储结构

一般是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系,比如双亲与孩子的关系,左右兄弟关系。

一般适用于完全二叉树,如下图所示。
在这里插入图片描述
很明显这种存储结构存在巨大的空间浪费。

5.2、二叉树的链式存储结构(二叉链表)

既然顺序存储结构适用性不够好,我们考虑链式存储结构。
二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是比较自然的想法,我们称这样的链表叫做二叉链表。
结点结构图如下:

在这里插入图片描述
我们看看结构示意图:
在这里插入图片描述

6、二叉树的遍历方法

6.1、前序遍历

规则是若二叉树为空,则空操作返回,否则先访问根结点,然后谦虚遍历左子树,在前序遍历右子树。
遍历顺序为,ABDGHCEIF的案例如下:
在这里插入图片描述
对应的算法片段:

//二叉树的前序遍历递归算法
void preOrderTraverse(BigTree t){
	if(t==NULL){
		return;
	}
	printf("c%",t-data);
	preOrderTraverse(t->leftChild);
	preOrderTraverse(t->rightChild);
}

6.2、中序遍历

规则是二叉树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
遍历顺序为GDHBAEICE的案例如下:

在这里插入图片描述
对应的算法片段:

//二叉树的中序遍历递归算法
void preOrderTraverse(BigTree t){
	if(t==NULL){
		return;
	}
	preOrderTraverse(t->leftChild);
	printf("c%",t-data);
	preOrderTraverse(t->rightChild);
}

6.3、后序遍历

规则是若二叉树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根节点。
遍历顺序为GHDBIEFCA的案例如下:

在这里插入图片描述
对应的算法片段:

//二叉树的后序遍历递归算法
void preOrderTraverse(BigTree t){
	if(t==NULL){
		return;
	}
	preOrderTraverse(t->leftChild);
	preOrderTraverse(t->rightChild);
	printf("c%",t-data);
}

6.4、层序遍历

规则是若二叉树为空,则空操作返回,否则从树的第一层,也就是根节点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
遍历顺序为ABCDEFGHI的案例如下:

在这里插入图片描述
对应的算法片段:

//TODO 

7、构建一颗二叉树

//TODO 待补充

8、树、森林与二叉树的转换

8.1、树转换为二叉树

主要步骤:
1、加线。所有兄弟结点之间加一条线;
2、去线。对树中每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线;
3、层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子。

在这里插入图片描述

8.2、森林转换为二叉树

1、把每个树转换为二叉树。
2、第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连起来。当所有的二叉树连接起来后就得到了由森林转换来的二叉树。

在这里插入图片描述

8.3、二叉树转换为树

1、加线。若某结点的左孩子结点存在,则将这个左孩子的右孩子的结点、右孩子的右孩子的结点
、。。。反正就是左孩子的n个右孩子结点都作为此结点的孩子。将该结点与这些右孩子结点用线连接起来。
2、去线。删除原二叉树中所有结点与其右孩子的连线。
3、层次调整。使之结构层次分明。

在这里插入图片描述

8.4、二叉树转换为森林

1、从根节点开始,若右孩子存在,则把与右孩子的连线删除,再查看分离后的二叉树,若右孩子存在,则连线删除。。。。 ,直到所有右孩子连线都删除为止,得到分离的二叉树。

在这里插入图片描述

9、赫夫曼树

从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作路径长度,
树的路径长度就是从树根到每一个结点的路径长度之和。
带权路径长度WPL最小的二叉树称作赫夫曼树,别名也叫最优二叉树。
赫夫曼算法描述:
1、根据给定的n个权值{w1,w2,…,wn}构成n棵二叉树的集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi的根结点,其中左右子树均为空。
2、再F中选取两棵根结点的权值最小的树作为左右子树构造一颗新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根节点的权值之和。
3、在F中删除这两棵树,同时将新得到的二叉树加入F中。
4、重复2和3的步骤,直到F只含一棵树为止。这棵树就是赫夫曼树。

强烈建议去阅读**《大话数据结构》**这本书,关于赫夫曼编码推导都右详细的案例分析,比较经典。

系统化的学习真的太累了,但是贵在坚持,即使是这样看完一章整理一章的内容,本博主都觉得基本上都是看懂了之后才进行整理的。每一篇文章的形成过程,上午看完这一章内容,下午整理文章发布博客,基本上8个小时才算勉强够。而且博主还是第二遍学习,信息量很大,非常费脑子。




码字不易,求个关注。

在这里插入图片描述

微信公众号: 一粒尘埃的漫旅

里面有很多想对大家说的话,就像和朋友聊聊天。
写代码,做设计,聊生活,聊工作,聊职场。
我见到的世界是什么样子的?
搜索关注我吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值