数据结构8-一篇搞定二叉树

文章目录

1. 二叉树概述

1.1 二叉树基本概念

在这里插入图片描述

1.2 几个特殊的二叉树

1.2.1 满二叉树与完全二叉树

在这里插入图片描述

注意:

  1. 满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。
  2. 完全二叉树和满二叉树都满足有结点i,则2*i为其左孩子结点,2*i+1为其右孩子节点,i/2向下取整为其父结点。
  3. 完全二叉树和满二叉树中 i <= n/2下取整 为分支结点【i <= n/2下取整为最后一个分支结点的序号】,i > n/2 为叶子结点。
  4. 完全二叉树中度为1的结点数要么为1要么为0,即n1 = 1 或者0

1.2.2 二叉排序树 (BST)

在这里插入图片描述

注意:

  1. 二叉排序树简称BST,有时候题目只会说BST。
  2. 用逐点插入法构造二叉排序树时,若先后插入的关键字有序,则二叉树排序树的深度最大。
  3. n个结点的二叉排序树,关键字的比较次数不超过n ,即最多为n,也就是如下两种情况
    在这里插入图片描述
  4. 二叉排序树的查找效率与树的高度有关,因为n个结点的二叉排序树高 log2(n + 1) <= h <= n, 所以其算法复杂度也界于上面之间。
    【为log2(n + 1) 的情况就是把它当作一颗完全二叉树来算得到的,具体可见完全二叉树部分】
    所以理想情况下的二叉排序树树高为: log2(n + 1) 向上取整
  5. 在任意一颗非空二叉排序树中T1中,删除结点v之后,得到二叉排序树T2,再将v插入T2得到二叉排序树T3 :
    1. 若v是叶子结点,则T1一定与T3相同。
    2. 若v是非叶子结点,则T1一定与T3不同。 【因为插入的必然是叶子结点】

1.2.3 平衡二叉树

在这里插入图片描述

注意:

  1. 平衡二叉树简称AVL树,是特殊的排序二叉树,即每个结点的左右子树高度差<=1 的排序二叉树。目的是提高排序树的搜索效率,即O(log2(n + 1)) = O(log2n)
  2. n个结点的平衡二叉树的高度为
    1. 最低: h >= log2(n + 1)
    2. 最高:n0 = 0, n1 = 1, n2 = 2, nh = nh-1 + nh-2 + 1 其对应的图如下:
      在这里插入图片描述
  3. 在任意一颗非空平衡二叉树中T1中,删除结点v之后,得到平衡二叉树T2,再将v插入T2得到平衡二叉树T3 :
    1. 若v是叶子结点,则T1与T3可能相同。 【可能导致不平衡发生旋转】
    2. 若v是非叶子结点,则T1与T3可能相同。 【可能旋转后相同】

1.3 二叉树与完全二叉树的重要性质

1.3.1 二叉树重要性质

在这里插入图片描述

注意:

  1. n0 = n2 + 1,
  2. 总结点树n与n1的奇偶性相反。利用这个性质可以直接做题,比如:
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.3.2 完全二叉树的重要性质

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1.4 二叉树的物理实现分类

* 根据二叉树的物理存储分为如下几类
	1. 顺序存储的二叉树
		1. 数组动态分配的顺序存储二叉树
		2. 数组静态态分配的顺序存储二叉树
		
		* 注意:顺序存储的二叉树适用于存储完全二叉树,否则空间利用率低。

	2. 链式存储的二叉树
		1. 结点静态分配的链式存储的二叉树
		2. 结点动态分配的链式存储的二叉树

1.4 小结

在这里插入图片描述

2. 顺序存储的二叉树

数组动态分配与数组动态分配的顺序表类似,之前在栈和队列中也都实现了,应该不难了。这里就实现常见的数组静态分配的顺序存储的二叉树。

2.1 数组动态分配的顺序存储二叉树

2.1.1 初始化与判空

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. 链式存储的二叉树

介绍结点动态分配的链式存储的二叉树。

3.1 结点动态分配的链式二叉树

3.1.1 初始化

在这里插入图片描述

注意:n个结点的二叉链表共有n + 1个空链域。

在这里插入图片描述
在这里插入图片描述

4. 二叉树的遍历

* 什么是遍历?
	* 简单来说:遍历是按照某种次序把所有结点都访问一遍。

* 二叉树的遍历
	* 二叉树的特性
		* 特性
			1. 空二叉树
			2. “根结点 + 左子树 + 右子树”组成的二叉树
	* 根据二叉树的递归特性遍历二叉树
	* 分类
		1. 前序遍历 --- 根左右
		2. 中序遍历 --- 左根右
		3. 后序遍历 --- 左右根

4.1 前/中/后序遍历

4.1.1 前序遍历

在这里插入图片描述

4.1.2 中序遍历

在这里插入图片描述

4.1.3 后序遍历

在这里插入图片描述

4.1.4 前/中/后序遍历的应用

4.1.4.1 对语法分析树求前/中/后缀表达式

在这里插入图片描述

4.1.4.2 求树的深度(后序遍历)

在这里插入图片描述

4.1.5 小结

在这里插入图片描述

注意:

  1. 前、中、后序遍历都是递归,故用到了栈(当然递归也可以不用栈实现)。在线索化后,只有后序线索树的遍历仍然需要栈。
  2. 求树的深度和祖先结点与子孙结点间的路径都用后序遍历。

4.2 层次遍历

在这里插入图片描述

5. 逆推二叉树

逆推二叉树:给出某二叉树的几种遍历结果,要求推出二叉树的结构。

5.1 任意一种遍历(前/中/后/层序遍历)推不出二叉树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结论:若只给出一颗二叉树的 前/中/后/层 序遍历中的一种,不能唯一确定一颗二叉树。

5.2 组合遍历逆推二叉树

在这里插入图片描述
中序遍历与其他任意一种遍历的组合能够唯一确定二叉树形态。

主要是因为:

  1. 中序遍历能够以根结点划分出左右子树,而其他的遍历都不能。
  2. 中序遍历不能判断出根结点,其他遍历能够判断出根结点。

注意:其中非中序是入栈顺序,中序是出栈顺序。

  • 问:先序序列为a,b,c,d的不同二叉树个数为__
  • 答:1/(n+1) * Cn到2n = 14

两者结合,就能唯一确定二叉树形态。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:核心就是根据非中序遍历找到树的根结点,再根据中序序列划分左右子树,再找到左右子树根结点。如此反复。

在这里插入图片描述

结论:前序、后续、层序序列的两两组合无法唯一确定一颗二叉树。

注意:虽然前序和后序的组合无法确定一个二叉树,但是其能确定二叉树的父子关系。
在这里插入图片描述

6. 线索二叉树

6.1 线索二叉树概述

6.1.1 线索二叉树的作用

* 先介绍一些专有名词:
	1. 前序前驱:假设现在有一个二叉树,其中一个结点为A,要求A的前序前驱,指的是二叉树进行前序遍历得到的
                次序中(这个次序是一个线性结构)A的前驱就叫A结点的中序
	2. 中序前驱:和前序前驱类似。
	3. 后序前驱:和前序前驱类似。

对于某个二叉树,如果知道该二叉树的某个结点地址,现在要得到其父结点的地址,显然这个通过之前的方法我们是无法得到的。但是,在某些情况下,我们需要频繁获取某结点的 前/中/后 序前驱结点或者 前/中/后 序后继结点。这个时候就需要用到线索二叉树。

其实,线索二叉树的实现很简单。之前说过,链式存储的二叉树如果有n个结点,那么会有n + 1个空指针域。这些空指针域中,如果是左指针,则指向 前/中/后 序前驱结点;如果是右指针,则指向 前/中/后 序后继结点。

下面是一个中序线索二叉树的示意图:
在这里插入图片描述

6.1.2 线索二叉树的存储结构

在这里插入图片描述

注意:线索二叉树是一种物理结构。

6.1.3 三种线索二叉树

三种线索二叉树对应的是

  1. 前序线索二叉树
  2. 中序线索二叉树
  3. 后序线索二叉树

注意:没有层次线索二叉树。

三种二叉树的对比:
在这里插入图片描述

注意:有左右子树的二叉树中,中序线索二叉树有两个null,而前序线索二叉树和后序线索二叉树只有一个null.

  • 中序:左根右。根结点的左子树的最左边的一个结点的前驱不存在,导致一个null,根结点右子树的最右边一个结点没有后继,导致一个null
  • 前序:根左右。根结点没有前驱,但是根结点有左右孩子,所以不会导致null,只有右子树最右边的的结点没有后继会导致一个null
  • 后序:与前序类似。

6.1.4 小结

在这里插入图片描述

6.2 二叉树线索化

6.2.1 中序线索化

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:二叉树线索化是将其n+1个空指针都线索化,其遍历的第一个结点的左指针和最后一个结点的右指针都需要线索化,这两个地方是指向NULL,但是也是线索化。

6.2.2 前序线索化

前序线索化和中序线索化差不多,但是前序线索化有一点需要特别注意。就是容易出现“爱的魔力转圈圈(死循环)”。

  1. “根”:因为前序遍历是先处理根结点,那么此时根节点B的左指针和右指针已经被处理了
  2. “左”:此时左指针是其前序前驱结点A,再访问其左指针。
  3. “右”:此时右指针是B. 这样就形成了死循环

解决方法是:在进行“根左右”的左之前判断节点的ltag == 0,是则让其访问左指针。如下图所示
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

6.2.3 后续线索化

在这里插入图片描述
在这里插入图片描述

注意:二叉树的前、中、后序遍历都需要栈;但是二叉树线索化后只有后序线索树的遍历还需要栈。

6.2.4 小结

在这里插入图片描述

6.3 线索二叉树找前驱/后继

6.3.1 中序线索二叉树

在这里插入图片描述
在这里插入图片描述

6.3.2 先序线索二叉树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.3.3 后序线索二叉树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.3.4 小结

在这里插入图片描述

7. 树、森林与二叉树的关系

7.1 树

7.1.1 树的存储

* 树的存储有三种方法
	1. 双亲表示法:每个结点存放数据和父节点的指针
	2. 孩子表示法:每个结点存放数据和孩子结点的指针
	3. 孩子兄弟表示法:每个结点存放数据、左指针存最左边的孩子、右指针存兄弟结点
		* 注意:该方法使用的多,因为其可以实现 森林/树 与 二叉树 的转换
7.1.1.1 双亲表示法

在这里插入图片描述

双亲表示法:每个结点中保存指向双亲结点的“指针”

删除结点时,处理删除该结点之外,应该把数组的最后一个元素放在这个空位置,如果最后一个结点有孩子,还需要修改孩子结点的父指针。如果删除的是非叶子结点,等价于删除该子树,所以,还需要把其子孙结点都删除。

在这里插入图片描述

7.1.1.2 孩子表示法

在这里插入图片描述
处理情况和双亲表示法类似。

7.1.1.3 孩子兄弟表示法(森林/树 <–>二叉树)

孩子兄弟表示法最常用的存储方法,因为其存储树/森林时将其转为了二叉树,这样我们可以利用二叉树的很多性质。

注意:孩子兄弟表示法不仅可以存储树,也可以存储森林。

在这里插入图片描述
在这里插入图片描述

7.1.2 树的遍历

* 树的遍历
	1. 深度优先遍历
		1. 先根遍历
		2. 后根遍历
		* 注意:树没有中根遍历的说法
	2. 广度优先遍历
		1. 层序遍历
7.1.2.1 先根遍历

在这里插入图片描述

7.1.2.2 后根遍历

在这里插入图片描述

注意:是与二叉树的中序相同,不是后序遍历。

7.1.2.3 层序遍历

在这里插入图片描述
层序遍历一般都需要使用一个队列来实现。

7.2 森林

7.2.1 森林的存储(孩子兄弟表示法)

森林的存储只有孩子兄弟表示法,比树少双亲表示法和孩子表示法。
关键点:记住是用的孩子兄弟表示法,左孩子,右兄弟;并且使用此方法会将森林转为二叉树的形式。
在这里插入图片描述
在这里插入图片描述

7.2.2 森林的遍历

7.2.2.1 先序遍历

在这里插入图片描述
森林的先序遍历等于二叉树的先序遍历,所以森林的先序遍历 = 二叉树的先序遍历 = 树的先序遍历

7.2.2.2 中序遍历

在这里插入图片描述
森林的中序遍历等于二叉树的中序遍历,所以森林的先序遍历 = 二叉树的先序遍历 = 树的后根遍历

7.3 小结

在这里插入图片描述
在这里插入图片描述

8. 二叉排序树(BTS)

8.1 二叉排序树的查找

在这里插入图片描述
在这里插入图片描述

8.2 二叉排序树的插入

在这里插入图片描述

注意:如果插入的数和二叉排序树中的数相等,则插入失败。

在这里插入图片描述
二叉排序树的构造可以不断使用插入方法实现。

8.3 二叉排序树的删除

在这里插入图片描述

8.4 二叉排序树的效率分析

在这里插入图片描述
二叉排序树的最坏效率不会超过树的高度,最好效率是logn即O(logn)<= 算法复杂度 <= O(h)
其实,最好情况logn就是该排序树为平衡二叉树的时候。

在这里插入图片描述

8.5 小结

在这里插入图片描述

注意:

  1. 在非空二叉排序树T1中,删除一个非叶子结点v得到二叉排序树T2,再将v插入T2得到二叉排序树T3,则T1T3一定不同。
  2. 在非空二叉排序树T1中,删除一个叶子结点v得到二叉排序树T2,再将v插入T2得到二叉排序树T3,则T1T3一定相同。
  3. 二叉排序树中,每个结点的左子树的所有结点小于该结点,该结点的右子树的所有结点都大于该结点。因为(左根右)根(左根右)
  4. 怎样构造二叉排序树深度最大? 插入的结点们有序。(从大到小,或从小到大)
  5. 有n个结点的二叉排序树,最多进行n次比较。

9. 平衡二叉树(AVL)

9.1 平衡二叉树的定义

在这里插入图片描述

注意:平衡因子是 左 - 右

9.2 平衡二叉树的插入

在这里插入图片描述
在这里插入图片描述

9.3 调整最小不平衡子树

在这里插入图片描述

9.3.1 LL(右旋)

在这里插入图片描述
在这里插入图片描述

9.3.2 RR(左旋)

在这里插入图片描述
在这里插入图片描述

9.3.3 LR(先左旋再右旋)

在这里插入图片描述

9.3.4 RL(先右旋再左旋)

在这里插入图片描述

9.3.5 代码思路

在这里插入图片描述

通过上面的左旋和右旋可以实现LR、RL的平衡。

9.3.6 小结

在这里插入图片描述

9.4 平衡二叉树查找效率分析

在这里插入图片描述

9.5 小结

在这里插入图片描述

10. 哈夫曼树

10.1 相关概念

在这里插入图片描述

10.2 哈夫曼树的构造

在这里插入图片描述
哈夫曼树每个结点的左右孩子无序,即可以左大右小,也可以左小右大,导致同样的结点构造的哈夫曼树不唯一。

注意:哈夫曼树不一定是二叉树,也可能是度为m的树。如果是度为m的哈夫曼树,且有叶子结点,有如下特点:

  1. 每个非叶子结点的度都为m,所以不存在度为1, 2, ..., m - 1 的结点
  2. 总结点数为mX + 1X为非叶子结点的个数,加1是指根结点),同时总结点数也为:X + n
    mX + 1 = X + n, 解得X = (n-1)/(m-1) 向上取整
    最终总结点数为:(n-1)/(m-1) + n 向上取整

10.3 哈夫曼编码

在这里插入图片描述
在这里插入图片描述

10.4 小结

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ElegantCodingWH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值