数据结构 | 树与二叉树

文章目录

树的定义和基本术语

在这里插入图片描述

树的基本概念

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

在这里插入图片描述

树形逻辑结构的应用

在这里插入图片描述

结点之间的关系描述

在这里插入图片描述

  • 两个结点之间的路径是单向的,只能从上往下,且路径长度代表的含义是经过了几条边

结点和树的属性描述

在这里插入图片描述

有序树 v.s. 无序树

在这里插入图片描述

树 v.s. 森林

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

树的性质

在这里插入图片描述

  • 除了根结点外,每个结点头上都会有一个“天线”,所以结点数 = 总度数 + 1
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

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

二叉树的定义和基本术语

在这里插入图片描述

二叉树的基本概念

在这里插入图片描述

二叉树的五种形态

在这里插入图片描述

几个特殊的二叉树

满二叉树 v.s. 完全二叉树

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

二叉排序树

在这里插入图片描述

平衡二叉树

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

二叉树的性质

二叉树的常考性质

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

完全二叉树的常考性质

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

  • 一个是根据两层满二叉树数量夹逼得来,一个是根据同一层的完全二叉树数量夹逼得来

在这里插入图片描述

  • 完全二叉树中度为1的结点 n1 只能有0个或者1个,而且根据上面的结论,n0 = n2+1 我们可以得到 n0 + n2 = 2n2 +1一定是奇数
  • 若完全二叉树的结点总数为偶数,我们可以知道度为1的结点n1的数量为1;若完全二叉树的结点总数为奇数,我们可以知道度为1的结点n1的数量为0;
    在这里插入图片描述

二叉树的存储结构

在这里插入图片描述

二叉树的顺序存储

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

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

  • 此时不能用结点的总数判断左右结点,但是我们可以通过结点的isEmpty字段来判断

在这里插入图片描述

  • 此时会造成存储空间的大量浪费
    在这里插入图片描述

二叉树的链式存储

在这里插入图片描述

  • n个结点,每个结点都会有左右两个孩子指针,所以n个结点会有2n个指针域
  • 但是n个结点由于根节点的原因只会有n-1个结点上有指针相连
  • 所以n个结点的二叉链表共有n+1个空链域【构造线索二叉树】
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

二叉树的遍历

在这里插入图片描述

什么是遍历

在这里插入图片描述

二叉树的遍历

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

  • 对算术表达式的分析书进行先序遍历,可以得到这个算术表达式的前缀表达式
  • 对算术表达式的分析书进行中序遍历,可以得到这个算术表达式的中缀表达式(需要加界限符)
  • 对算术表达式的分析书进行后序遍历,可以得到这个算术表达式的后缀表达式

先序遍历

在这里插入图片描述

  • 可以在visit函数中添加其他逻辑完成更复杂的功能

先序遍历的函数调用关系

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

  • 空间复杂度为O(h+1),h为二叉树的高度,+1是因为最下面的叶子结点还有两个空的左右子树也需要把它们压到栈顶

中序遍历

在这里插入图片描述

后序遍历

在这里插入图片描述

求遍历序列

  • 红色箭头:第一次经过结点
  • 绿色箭头:第二次经过结点
  • 紫色箭头:第三次经过结点
  • 前序遍历要求第一次经过结点的时候就访问该结点
  • 中序遍历要求第二次经过结点的时候就访问该结点
  • 后序遍历要求第三次经过结点的时候就访问该结点
求先序遍历序列

在这里插入图片描述

求中序遍历序列

在这里插入图片描述

求后序遍历序列

在这里插入图片描述

遍历算法的应用举例

  • 求树的深度
    在这里插入图片描述
    在这里插入图片描述

二叉树的层序遍历

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

由遍历序列构造二叉树

不同二叉树的中序遍历序列

在这里插入图片描述

不同二叉树的前序遍历序列

在这里插入图片描述

不同二叉树的后序遍历序列

在这里插入图片描述

不同二叉树的层序遍历序列

在这里插入图片描述

由遍历序列构造二叉树

在这里插入图片描述

前序+中序遍历序列

在这里插入图片描述

  • 前序序列当中最先出现的结点肯定是根节点,这样的话我们就可以确定根节点在中序遍历序列的位置
  • 那么在中序序列中根节点左边出现的这些节点肯定就是左子树中的结点,右边出现的结点肯定就是右子树中的结点
  • 我们就可以根据这两条结论递归地构造出二叉树
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
后序+中序遍历序列

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

层序+中序遍历序列

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

若前序、后序、层序两两组合?

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

线索二叉树

在这里插入图片描述

二叉树的中序遍历序列

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

  • 此时q指向的结点就是p所指向的结点,那么此时pre结点所指向的就是p结点的中序遍历后继
    在这里插入图片描述
  • 如果要找的是p结点的中序遍历后继,我们可以把代码再执行一步,直到pre指向的结点就是p所指向的结点,那么此时q结点所指向的就是p结点的中序遍历后继

中序线索二叉树

在这里插入图片描述

  • 找节点的前驱后继变得更方便了,遍历也变得更方便了

线索二叉树的存储结构

在这里插入图片描述

中序线索二叉树的存储

在这里插入图片描述

先序线索二叉树

在这里插入图片描述

先序线索二叉树的存储

在这里插入图片描述

后序线索二叉树

在这里插入图片描述

后序线索二叉树的存储

在这里插入图片描述

三种线索二叉树的对比

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

二叉树的线索化

在这里插入图片描述

用土办法找到中序前驱

在这里插入图片描述

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

中序线索化

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

先序线索化

在这里插入图片描述

  • 先序线索化由于访问并且修改完根节点之后再遍历左右子树结点,可能会出先“爱的魔力转圈圈”的问题,即访问完根节点之后我们需要访问左子树,但是左子树此时为空且在我们访问根节点的时候已经把左孩子线索化为了先序前驱,所以会陷入死循环,当我们访问完一个节点之后又会去访问它的前驱,不断访问这两个结点
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

后序线索化

在这里插入图片描述

  • 后序线索化当中肯定不会出现先序线索化中出现的转圈的问题,原因在于当我们在访问一个结点q的时候,这个结点它的左孩子那条路我们肯定已经处理完了,右孩子那条路我们肯定也已经处理完了,所以我们处理完之后不可能再回头去访问其左孩子所指向的那棵子树
    在这里插入图片描述
    在这里插入图片描述
  • 只有先序线索化才会出现“爱的魔力转圈圈我”问题,原因在于当我们在visit一个结点q的时候,这个结点q的左子树,也就是左孩子指针所指的这条路肯定在之前就已经被遍历且处理过了

线索二叉树找前驱/后继

在这里插入图片描述

中序线索二叉树找中序后继

在这里插入图片描述

  • 如果p结点已经被线索化了,那么p->rchild就是它的中序后继
  • 如果p结点没有被线索化则证明p结点一定有右孩子或者右子树,根据中序遍历的规则,此时p结点的中序后继就是其右孩子或者右子树中最左下角的结点
    在这里插入图片描述

中序线索二叉树找中序前驱

在这里插入图片描述

  • 如果p结点已经被线索化了,那么p->lchild就是它的中序前驱
  • 如果p结点没有被线索化则证明p结点一定有左孩子或者左子树,根据中序遍历的规则,此时p结点的中序前驱就是其左孩子或者左子树中最右下角的结点
    在这里插入图片描述

先序线索二叉树找先序后继

在这里插入图片描述

  • 如果p结点已经被线索化了,那么p->rchild就是它的先序后继
  • 如果p结点没有被线索化则证明p结点一定有右孩子或者右子树,根据先序遍历的规则,此时p结点的先序后继会有两种情况:
    1. 若p点有左孩子,根据先序遍历的规则,此时的先序后继就是左孩子或者左子树的根节点(也是p的左孩子)
    2. 若p点没有左孩子,根据先序遍历的规则,此时的先序后继就是右孩子或者是右子树的根节点(也是p的右孩子)

先序线索二叉树找先序前驱

在这里插入图片描述

  • 如果p结点已经被线索化了,那么p->rchild就是它的先序后继
  • 但是如果p结点没有被线索化则证明p结点一定有右孩子或者右子树,根据先序遍历的规则,此时p的左子树和右子树都只能是p的先序后继,而不可能是p结点的先序前驱,因此我们不可能在p的左右子树中找到p结点的先序前驱,只能用土办法从头开始重新进行一次先序遍历
  • 但是其实我们也可以把二叉链表改造为三叉链表,每个结点都会有指向其父节点的指针,此时又会出现4种情况:
    1. 在这里插入图片描述
    2. 在这里插入图片描述
    3. 在这里插入图片描述
      此时我们应该优先地往右边走,如果右边的结点没有了那我们就应该优先地往左边走,以此类推找到最下面一层的结点
    4. 在这里插入图片描述

后序线索二叉树找后序前驱

在这里插入图片描述

  • 如果p结点已经被线索化了,那么p->lchild就是它的后序前驱
  • 如果p结点没有被线索化则证明p结点一定有左孩子或者左子树,根据后序遍历的规则,此时p结点的后序前驱会有两种情况:
    1. 若p点有右孩子,根据后序遍历的规则,此时的后序前驱就一定是它右子树当中按照后序遍历最后一个被访问到的结点,也就是p的右孩子
    2. 若p点没有右孩子,根据后序遍历的规则,此时的后序前驱就一定是左子树当中按照后序遍历最后一个被访问的结点,显然就是它的左孩子

后序线索二叉树找后序后继

在这里插入图片描述

  • 如果p结点已经被线索化了,那么p->rchild就是它的后序后继
  • 但是如果p结点没有被线索化则证明p结点一定有右孩子或者右子树,根据后序遍历的规则,此时p的左子树和右子树都只能是p的后序前驱,而不可能是p结点的后序后继,因此我们不可能在p的左右子树中找到p结点的后序后继,只能用土办法从头开始重新进行一次先序遍历
  • 但是其实我们也可以把二叉链表改造为三叉链表,每个结点都会有指向其父节点的指针,此时又会出现4种情况:
    1. 在这里插入图片描述

    2. 在这里插入图片描述

    3. 在这里插入图片描述
      按照后序遍历的规则此时p结点的后序后继就是其右兄弟子树当中按照后序遍历的规则第一个被访问的结点,也就是我们从根结点出发尽可能地往左走,如果往左地路没了但是还有往右的路的话,那我们还要继续往右走,用这种方式找到最下面一个叶子结点,就是第一个被后序遍历的结点

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

树的存储结构

在这里插入图片描述

树的逻辑结构

在这里插入图片描述

双亲表示法(顺序存储)

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

  • 双亲表示法删除数据元素方案一:把删除元素结点的双亲指针设为-1,表示这个位置是空的
    在这里插入图片描述
  • 双亲表示法删除数据元素方案二【better】:把尾部的数据移上来填补删除元素的这个空白,这样可以保证前面所有的存储单元都是有效的
  • 注意删除操作后也要把PTree.n的值改为相应的数值
  • 如果删除的数据元素不是叶子节点,证明我们要把以这个结点为根节点的整棵子树都删掉,我们需要找到这个节点的其他子孙节点,把它们都统统删除

孩子表示法(顺序+链式存储)

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

孩子兄弟表示法(链式存储)

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

森林和二叉树的转换

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

树和森林的遍历

在这里插入图片描述

树的先根遍历

在这里插入图片描述

树的后根遍历

在这里插入图片描述

树的层次遍历

在这里插入图片描述

森林的先序遍历

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

森林的中序遍历

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

哈夫曼树

在这里插入图片描述

带权路径长度

在这里插入图片描述

哈夫曼树的定义

在这里插入图片描述

哈夫曼树的构造

在这里插入图片描述

  • n个结点,需要n-1次的合并才可以全部合并完,每次合并会产生一个分支节点,所以哈夫曼树结点总数是2n-1,非叶子节点数n-1
    在这里插入图片描述

哈夫曼编码

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

并查集

在这里插入图片描述

漏网之鱼:逻辑结构——集合

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

用互不相交的树表示多个“集合”

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

在这里插入图片描述

  • 使用“双亲表示法”来表示“并查集”的好处就是并和查的这两个操作实现起来都十分方便,查这个操作只需要一路往上找到根结点即可,并的这个操作就是要把其中一棵树的根节点让其指向另一颗树的根节点

并查集的存储结构

在这里插入图片描述

并查集的基本操作

在这里插入图片描述

并查集的代码实现——初始化

在这里插入图片描述

并查集的代码实现——并和查

在这里插入图片描述

时间复杂度分析

在这里插入图片描述

  • 最坏的时间复杂度的时间开销和树的高度h相关,因此如果我们想要优化这个并查集的效率的话,我们可以在构造的时候尽可能让树长得“矮胖”点,而不是“瘦高”点

Union操作的优化

  • 一种想法:我们可以让小树合并到大树上面,因为如果是大树合并到小树上的话,有可能会使合并后整棵树的树高h增加
    在这里插入图片描述
  • 现在问题来了,我们如何表示一棵树的大小呢?我们可以用根节点的绝对值表示树的结点总数
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

并查集的终极优化

拓展:Find操作的优化——压缩路径

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

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

并查集的优化

在这里插入图片描述

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ErizJ

觉得好的话给小弟一点鼓励吧

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

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

打赏作者

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

抵扣说明:

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

余额充值