04 树结构

第6章 树

6.1 开场白

6.2 树的定义

  1. 树是一种一对多的数据结构

  2. 树(Tree) 是n(n≥0) 个结点的有限集n=0时称为空树。 在任意一棵非空树中:

    • 有且仅有一个特定的称为**根(Root)**的结点;

    • 当n>1时, 其余结点可分为m(m>0) 个互不相交的有限集T1、 T2、 ……、 Tm, 其中每一个集合本身又是一棵树, 并且称为根的子树(SubTree)。

  3. 示意图

    image-20220729103927747

    image-20220729103954787

    • 子树T1和子树T2就是根结点A的子树。 当然, D、 G、 H、 I组成的树又是B为根结点的子树, E、 J组成的树是以C为根结点的子树
  4. 注意

    1. .n>0时根结点是唯一

    2. m>0时, 子树的个数没有限制, 但它们一定是互不相交的

      image-20220729104351220

6.2.1 结点分类

  • 结点拥有的子树数称为结点的度(De-gree)
  • 度为0的结点称为叶结点(Leaf) 或终端结点; 度不为0的结点称为非终端结点或分支结点除根结点之外, 分支结点也称为内部结点
  • 树的度是树内各结点的度的最大值。图中因为这棵树结点的度的最大值是结点D的度为3, 所以树的度也为3

image-20220729105018115

6.2.2 结点间关系

  • 结点的子树的根称为该结点的孩子(Child)该结点称为孩子的双亲(Parent)
  • 同一个双亲的孩子之间互称兄弟(Sibling)
  • 结点的祖先是从根到该结点所经分支上的所有结点。 所以对于H来说,D、 B、 A都是它的祖先。 反之, 以某结点为根的子树中的任一结点都称为该结点的子孙。 B的子孙有D、 G、 H、 I, 如下图所示。

image-20220729105619683

6.2.3 树的其他相关概念

  • 结点的层次(Level) 从根开始定义起, 根为第一层, 根的孩子为第二层。 若某结点在第l层, 则其子树就在第l+1层。
  • 其双亲在同一层的结点互为堂兄弟。 显然下图中的D、 E、 F是堂兄弟, 而G、 H、 I与J也是堂兄弟。
  • 树中结点的最大层次称为树的深度(Depth) 或高度, 当前树的深度为4。
  • 如果将树中结点的各子树看成从左至右是有次序的, 不能互换的, 则称该树为有序树, 否则称为无序树。
  • **森林(Forest)**是m(m≥0) 棵互不相交的树的集合。 对树中每个结点而言, 其子树的集合即为森林

image-20220729110145817

线性结构与树结构的对比

image-20220729110411760

6.3 树的抽象数据类型

image-20220729112051715

6.4 树的存储结构

三种表示方法:双亲表示法、 孩子表示法、 孩子兄弟表示法

6.4.1 双亲表示法

  1. 前提:在树结构中,除了根结点外, 其余每个结点, 它不一定有孩子, 但是一定有且仅有一个双亲
  2. 特点:以一组连续空间存储树的结点, 同时在每个结点中, 附设一个指示器指示其双亲结点在数组中的位置
  3. 结构:data是数据域, 存储结点的数据信息,parent是指针域, 存储该结点的双亲在数组中的下标。 约定根结点的指针域设置为-1

image-20220729112915389

  1. 结构代码:

image-20220729113309940

  1. 例子

image-20220729113450693

image-20220729113511907

特点:寻找结点的双亲的时间复杂度为O(1),寻找结点的孩子的时间复杂度为O(n)。可以通过增加孩子域帮忙寻找孩子

6.4.2 孩子表示法

  1. 方法:把每个结点的孩子结点排列起来, 以单链表作存储结构, 则n个结点有n个孩子链表, 如果是叶子结点则此单链表为空。 然后n个头指针又组成一个线性表, 采用顺序存储结构, 存放进一个一维数组

image-20220730101952842

  1. 结构定义代码

image-20220730102236587

  1. 这样的结构对于我们要查找某个结点的某个孩子, 或者找某个结点的兄弟, 只需要查找这个结点的孩子单链表即可。 对于遍历整棵树也是很方便的, 对头结点的数组循环即可。
  2. 双亲孩子表示法(孩子表示法的改进,便于找到双亲)

image-20220730102514378

6.4.3 孩子兄弟表示法

  1. 前提:任意一棵树, 它的结点的第一个孩子如果存在就是唯一的, 它的右兄弟如果存在也是唯一的。 因此, 我们设置两个指针, 分别指向该结点的第一个孩子此结点的右兄弟。(缺点,不方便找到双亲)

image-20220730102816763

  1. 结构代码

image-20220730102835902

  1. 示意图

image-20220730102903916

孩子兄弟表示法稍作变形就成为了二叉树

6.5 二叉树的定义

  1. 折半查找算法(猜100以内的整数,一定能在7次内猜出来)

image-20220730103330669

6.5.1 二叉树特点

  1. 二叉树的特点

    • 每个结点最多有两棵子树, 所以二叉树中不存在度大于2的结点

    • 左子树和右子树是有顺序的, 次序不能任意颠倒

    • 即使树中某结点只有一棵子树, 也要区分它是左子树还是右子树

  2. 二叉树的五种基本形态:空二叉树、只有一个根结点、根结点只有左子树、根结点只有右子树、根结点既有左子树又有右子树。

  3. 三结点树

image-20220730103923689

6.5.2 特殊二叉树

  1. 斜树:所有的结点都只有左子树的二叉树叫左斜树。 所有结点都是只有右子树的二叉树叫右斜树。两者统称为斜树。特点是结点的个数与二叉树的深度相同。(线性表结构可以理解为是树的一种极其特殊的表现形式)

image-20220730104825945image-20220730104849970

  1. 满二叉树:所有分支结点都存在左子树和右子树, 并且所有叶子都在同在层上。有以下特点:

    • 叶子只能出现在最下一层

    • 非叶子结点的度一定是2

    • 在同样深度的二叉树中, 满二叉树的结点个数最多, 叶子数最多

image-20220730104909573

  1. 完全二叉树:有n个结点的二叉树按层序编号编号为i(1≤i≤n) 的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同。特点如下:
    • 叶子结点只能出现在最下两层
    • 最下层的叶子一定集中在左部连续位置
    • 倒数二层, 若有叶子结点, 一定都在右部连续位置
    • 如果结点度为1, 则该结点只有左孩子, 即不存在只有右子树的情况
    • 同样结点数的二叉树, 完全二叉树的深度最小

image-20220730105527117


image-20220730105556672

6.6 二叉树的性质

6.6.1 二叉树性质1

在二叉树的第i层上至多有2i-1个结点(i≥1)

6.6.2 二叉树性质2

深度为k的二叉树至多有2k-1个结点(k≥1) 。

6.6.3 二叉树性质3

对任何一棵二叉树T, 如果其终端结点数为n0, 度为2的结点数为n2, 则n0=n2+1。

n表示总的结点个数,n0表示叶结点的个数,n1表示度为1的结点个数,n2表示度为2的结点个数:

  • 分支线总数=n-1=n1+2n2。 由于n=n0+n1+n2, 故可推导出n0+n1+n2-1=n1+2n2。 所以n0=n2+1

6.6.4 二叉树性质4

具有n个结点的完全二叉树的深度为|log2n+1|(|x|表示不大于x的最大整数) 。

6.6.5 二叉树性质5

如果对一棵有n个结点的完全二叉树的结点按层序编号(从上到下,从左到右), 对任一结点i(1≤i≤n):

  • 如果i=1,则结点i是二叉树的根, 无双亲; 如果i>1,则|i/2|其双亲是结点。
  • 如果2i>n, 则结点i无左孩子(结点i为叶子结点) ; 否则其左孩子是结点2i。
  • 如果2i+1>n, 则结点i无右孩子; 否则其右孩子是结点2i+1。

6.7 二叉树的存储结构

6.7.1 二叉树顺序存储结构

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

  2. 例子(不存在的结点设置为空(“∧”)而已 )

image-20220730113612310

image-20220730113621428


image-20220730113730180


image-20220730113808626

注:顺序存储结构一般只用于完全二叉树,否则会造成空间的浪费

6.7.2 二叉链表

  1. 结点示意图

image-20220730114127950

  1. 结构定义代码

image-20220730114209452

  1. 结构示意图

image-20220730114302901

6.8 遍历二叉树

6.8.1 二叉树遍历原理

  1. 定义:二叉树的遍历(traversing binary tree) 是指从根结点出发, 按照某种次序依次访问二叉树中所有结点, 使得每个结点被访问一次且仅被访问一次

6.8.2 二叉树遍历方法

  1. 前序遍历:若二叉树为空, 则空操作返回, 否则先访问根结点, 然后前序遍历左子树, 再前序遍历右子树

image-20220731100924003

  1. 中序遍历:若树为空, 则空操作返回, 否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树, 然后是访问根结点, 最后中序遍历右子树

image-20220731101202734

  1. 后序遍历:若树为空, 则空操作返回, 否则从左到右先叶子后结点的方式遍历访问左右子树, 最后是访问根结点。

image-20220731101316744

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

image-20220731101425573

6.8.3 前序遍历算法

代码(递归实现,结合6.8.2的示意图理解)

image-20220731101844864

6.8.4 中序遍历算法

代码(递归实现,结合6.8.2的示意图理解)

image-20220731102638540

6.8.5 后序遍历算法

image-20220731102910274

6.8.6 推导遍历结果

  1. 性质:
    • 已知前序遍历序列和中序遍历序列, 可以唯一确定一棵二叉树
    • 已知后序遍历序列和中序遍历序列, 可以唯一确定一棵二叉树

但已知前序和后序遍历,不能确定一棵二叉树

6.9 二叉树的建立

  1. 前提:为了能让每个结点确认是否有左右孩子, 所以对它进行了扩展,将二叉树中每个结点的空指针引出一个虚结点, 其值为一特定值, 比如“#”。

image-20220731104316766

  1. 定义:称这种处理后的二叉树为原二叉树的扩展二叉树
  2. 方法:扩展二叉树就可以做到一个遍历序列确定一棵二叉树了。

image-20220731104523153

6.10 线索二叉树

6.10.1 线索二叉树原理

  1. 提出线索二叉树的原因:原二叉树空指针域浪费内存空间;且若想知道前驱后继关系,必须进行遍历
  2. 定义:
    • 把指向前驱和后继的指针称为线索, 加上线索的二叉链表称为线索链表, 相应二叉树称为线索二叉树
    • 对二叉树以某种次序遍历使其变为线索二叉树的过程称做是线索化

image-20220731105702355


image-20220731110057488

  1. 某一结点的lchild是指向它的左孩子还是指向前驱问题的解决方法:添加标志符

image-20220731110539937

image-20220731110846469

6.10.2 线索二叉树结构实现

  1. 结果定义代码

image-20220731111305604

  1. 中序遍历并进行线索化

image-20220731112646794

  1. 二叉树线索链表上添加一个头结点

image-20220731113030691

  1. 遍历代码

image-20220731113214047

image-20220731113320859

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

6.11.1 树转换为二叉树

具体步骤

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

image-20220802091416137

6.11.2 森林转换为二叉树

具体步骤:

  1. 把每个树转换为二叉树。
  2. 第一棵二叉树不动, 从第二棵二叉树开始, 依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子, 用线连接起来

image-20220802091921319

6.11.3 二叉树转换为树

具体步骤

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

image-20220802092859530

6.11.4 二叉树转换为森林

  1. 判断标准:如果二叉树的根结点有右孩子就是森林, 没有就是一棵树。
  2. 具体步骤:
    1. 从根结点开始, 若右孩子存在, 则把与右孩子结点的连线删除, 再查看分离后的二叉树, 若根节点右孩子存在, 则连线删除……, 直到所有右孩子连线都删除为止,得到分离的二叉树。
    2. 再将每棵分离后的二叉树转换为树即可

image-20220802093457809

6.11.5 树与森林的遍历

  1. 树的遍历分两种(以6.11.3右下角图为例):
    • 先根遍历 :先访问树的根结点, 然后依次先根遍历根的每棵子树:ABEFCDG
    • 后根遍历 :先依次后根遍历每棵子树, 然后再访问根结点:EFBCGDA
  2. 森林的遍历也分为两种(以6.11.4下面图为例)
    • 前序遍历:访问森林中第一棵树的根结点, 然后依次先根遍历根的每棵子树, 再依次用同样方式遍历除去第一棵树的剩余树构成的森林:ABCDEFGHJI
    • 后序遍历: 同前序遍历,只是把先根遍历改为了后根遍历:BCDAFEJHIG

当以二叉链表作树的存储结构时, 树的先根遍历和后根遍历完全可以借用二叉树的前序遍历和中序遍历的算法来实现

6.12 赫夫曼树及其应用

6.12.1 赫夫曼树

  1. 在编码中用到的特殊的二叉树称之为赫夫曼树,编码方法称为赫夫曼编码,平时所用的压缩和解压缩技术也都是基于赫夫曼的研究之上发展而来
  2. 赫夫曼树提高了数据处理的效率

6.12.2 赫夫曼树定义与原理

  1. 从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称做路径长度。(如下图的二叉树a中根结点到结点D的路径长度为4,二叉树b中根结点到结点D的路径长度为2
  2. 树的路径长度就是从根到每一结点的路径长度之和(如下图二叉树a的树路径长度为1+1+2+2+3+3+4+4=20。 二叉树b的树路径长度就为1+2+3+3+2+1+2+2=16)
  3. 结点的带权的路径长度(WPL)从该结点到根之间的路径长度与结点上权的乘积。(如下图二叉树a的WPL=5×1+15×2+40×3+30×4+10×4=315,二叉树b的WPL=5×3+15×3+40×2+30×2+10×2=220)

image-20220802100714519

  1. 寻找最优的赫夫曼树(WPL最小树)的具体过程:
    1. 先把有权值的叶子结点按照从小到大的顺序排列成一个有序序列, 即: A5,E10, B15, D30, C40。
    2. 头两个最小权值的结点作为一个新节点N1的两个子结点, 相对较小的是左孩子, 这里就是A为N1的左孩子, E为N1的右孩子, 如图所示。新结点的权值为两个叶子权值的和5+10=15。

image-20220802101530199

  1. 将N1替换A与E, 插入有序序列中, 保持从小到大排列。 即: N115, B15, D30,C40。
  2. 重复步骤2。 将N1与B作为一个新节点N2的两个子结点。 如图所示。 N2的权值=15+15=30。

image-20220802101937959

  1. 将N2替换N1与B, 插入有序序列中, 保持从小到大排列。 即: N230, D30,C40
  2. 重复步骤2。 将N2与D作为一个新节点N3的两个子结点。 如图所示。 N3的权值=30+30=60

image-20220802102116278

  1. 将N3替换N2与D, 插入有序序列中, 保持从小到大排列。 即: C40, N360。
  2. 重复步骤2。 将C与N3作为一个新节点T的两个子结点, 如图所示。 T即是根结点, 完成赫夫曼树的构造。

image-20220802102339971

赫夫曼算法描述:

  1. 根据给定的n个权值{w1,w2,…,wn}构成n棵二叉树的集合F={T1,T2,…,Tn}, 其中每棵二叉树Ti中只有一个带权为wi根结点, 其左右子树均为空
  2. 在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树, 且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
  3. 在F中删除这两棵树, 同时将新得到的二叉树加入F中。
  4. 重复2和3步骤, 直到F只含一棵树为止。 这棵树便是赫夫曼树。

6.12.3 赫夫曼编码

  1. 编码

image-20220802103639259

image-20220802103706409

  1. 解码:发送方和接收方必须要约定好同样的赫夫曼编码规则

    例如:接收到1001010010101001000111100时, 由约定好的赫夫曼树可知, 1001得到第一个字母是B, 接下来01意味着第二个字符是A

image-20220802103859032

6.13 总结回顾

6.14 结尾语

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值