数据结构复盘——第五章:树


第一部分:树和基本二叉树

1、树的基本术语

:某一节点子树(孩子、子节点)的个数。
树的度:树中结点的最大度数。

“结点”和“节点”这两种叫法意思是一样的,都可以用。
根节点:一棵(非空)树有且仅有一个的节点。
叶子结点:度为 0 的结点,也称为终端结点
分支结点:度不为 0 的结点,也称为非叶子节点非终端结点(根节点也可能属于分支节点)。

层数:根节点在第一层,根节点的子节点在第2层,以此类推。
深度:是从根结点开始自顶向下逐层累加的。
高度:是从叶结点开始自底向上逐层累加的。

有序树:树中任意节点的子节点之间有顺序关系。
无序树:树中任意节点的子节点之间无顺序关系。

树中两个结点之间的:
路径:由这两个结点之间所经过的结点序列构成。
路径长度:路径上所经过的边的个数

2、树的家族关系

孩子:节点的子树(子节点)。
双亲:节点是子树的双亲。
兄弟同一个双亲的孩子之间称为兄弟。
堂兄弟双亲在同一层的节点互为堂兄弟。
祖先:从根到此节点所经分支上的所有节点都称为此节点的祖先。
子孙:以某节点为根的子树中的任一节点都称为此节点的子孙。

3、基本的二叉树

二叉树的特点是每个结点至多只有两棵子树,即二叉树中不存在度大于2的结点。
二叉树的子树有左右之分,其次序不能颠倒

满二叉树 完全二叉树

树的高度为 n n n,且含有 2 n − 1 2^n-1 2n1个结点的二叉树;

树的每层都含有最多的结点;

叶子结点都集中在满二叉树的最下层;

除叶子结点外的其他结点度数均为2;

每个结点都与和它高度相同的满二叉树结点的编号对应;

叶子结点只可能在最下面两层出现;

度为1的结点最多只能有一个,且该节点只有左孩子;

若某结点只有左孩子,或者是叶子结点,那么只要存在编号大于该结点的结点一定是叶子结点;

结点个数为奇数时每个分支结点都含有左右孩子;

结点个数为偶数时只有编号最大的分支节点没有右孩子;

满二叉树也属于完全二叉树的一种,生成一个完全二叉树的口诀就是:“从上到下,从左到右”

4、二叉树的存储结构

(1)顺序存储结构:
二叉树的顺序存储
(2)链式存储结构:
二叉树的链式存储

5、二叉树的基本性质

(1)树中的结点数等于所有结点的度数加 1 。若树中结点总数为 n,则:n = 分支数(即度数之和)+1

【2010统考真题】在一棵度为4的树T中,若有20个度为4的结点,10个度为3的结点,1个度为2的结点,10个度为1的结点,则树T的叶子结点个数是(B)。
A.41
B.82
C.113
D.122
【分析】设叶子结点有k个,总结点数为n,则有: n = ( 20 + 10 + 1 + 10 ) + k = ( 20 ∗ 4 + 10 ∗ 3 + 1 ∗ 2 + 10 ∗ 1 ) + 1 n=(20+10+1+10)+k=(20*4+10*3+1*2+10*1)+1 n=(20+10+1+10)+k=(204+103+12+101)+1,得到 41 + k = 123 41+k=123 41+k=123,即 k = 82 k=82 k=82

【训练例题】已知一棵度为4的树中,度为 0、1、2、3 的结点数分别为 14、4、3、2,求该树的结点总数 n 和度为 4 的结点个数。
【分析】设度为4的结点个数为k,则有: n = ( 14 + 4 + 3 + 2 ) + k = ( k ∗ 4 + 2 ∗ 3 + 3 ∗ 2 + 4 ∗ 1 ) + 1 n=(14+4+3+2)+k=(k*4+2*3+3*2+4*1)+1 n=(14+4+3+2)+k=(k4+23+32+41)+1,得到 23 + k = 4 k + 17 23+k=4k+17 23+k=4k+17,即 k = 2 , n = 25 k=2,n=25 k=2n=25

上面第一条性质适用于所有树,而下面将介绍二叉树专有的五个性质。

(2)非空二叉树上的叶子结点数等于度为 2 的结点数加 1,即: n 0 = n 2 + 1 n_0 = n_2 +1 n0=n2+1

由性质一“结点总数 = 结点度数+1”,有: n = n 0 + n 1 + n 2 = ( n 0 ∗ 0 + n 1 ∗ 1 + n 2 ∗ 2 ) + 1 n = n_0 + n_1 + n_2 = (n_0*0+n_1*1+n_2*2)+1 n=n0+n1+n2=(n00+n11+n22)+1,左右消去后得上式。

(3)完全二叉树中结点 i 的左孩子编号为 2 i 2i 2i

设结点 i 是第k行的第x个,则它属于第 ( 2 k − 1 − 1 ) + x = 2 k − 1 + ( x − 1 ) (2^{k-1}-1)+x=2^{k-1}+(x-1) (2k11)+x=2k1+(x1) 个结点;
而它的左子节点是第k+1行的,属于第 ( 2 ( k + 1 ) − 1 − 1 ) + 2 ∗ ( x − 1 ) + 1 = 2 k + 2 ∗ ( x − 1 ) = 2 ∗ [ 2 k − 1 + ( x − 1 ) ] (2^{(k+1)-1}-1)+2*(x-1)+1=2^k+2*(x-1)=2*[2^{k-1}+(x-1)] (2(k+1)11)+2(x1)+1=2k+2(x1)=2[2k1+(x1)] 个结点;

(4)非空二叉树上第 k 层上至多有 2 k − 1 2^{k-1} 2k1 个结点(k≥1) 。

等比数列的通项。

(5)高度为 h 的二叉树至多有 2 h − 1 2^h-1 2h1 个结点(h≥1)。

由等比数列求和公式: a 1 − a n ∗ q 1 − q \frac{a_1-a_n*q}{1-q} 1qa1anq,有: 1 − 2 h − 1 ∗ 2 1 − 2 = 2 h − 1 \frac{1-2^{h-1}*2}{1-2}=2^h-1 1212h12=2h1

(6)具有 n 个结点(n>0)的完全二叉树的高度为: ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n \rfloor+1 log2n+1

由公式五可推知: 2 h − 1 ≤ n ≤ 2 h − 1 2^{h-1} \leq n \leq 2^h-1 2h1n2h1,因此 { h ≤ l o g 2 n + 1 = l o g 2 ( 2 n ) h ≥ l o g 2 ( n + 1 ) \begin{cases} h \leq log_2n+1=log_2(2n) \\ h \geq log_2(n+1) \end{cases} { hlog2n+1=log2(2n)hlog2(n+1)

6、二叉树的遍历

(1)先序遍历:若二叉树非空,则访问根结点,先根遍历左子树,后根遍历右子树——“根左右”
(2)中序遍历:若二叉树非空,则中根遍历左子树,访问根结点,中根遍历右子树——“左根右”
(3)后序遍历:若二叉树非空,则后根遍历左子树,后根遍历右子树,访问根结点——“左右根”
(4)层序遍历:从二叉树的根结点开始,按自上而下,从左到右的顺序进行遍历。

对于一个表达式,可用一个二叉树来表示,例如对于表达式: a − b / ( c + d ) + e ∗ f a-b/(c+d)+e*f ab/(c+d)+ef

我们不难发现,先序遍历后序遍历可以将中缀表达式转换成前缀表达式后缀表达式

【经典例题】例如,求先序序列(ABCDEFGHI)和中序序列(BCAEDGHFI)所确定的二叉树以及后序序列。

第一部分习题

  1. 已知一棵二叉树的后序序列为DABEC,中序序列为DEBAC,则先序序列为?

  2. 下列序列中,不能唯一地确定一棵二叉树的是(D) 。
    A.层次序列和中序序列
    B.先序序列和中序序列
    C.后序序列和中序序列
    D.先序序列和后序序列(不能没有中序序列来确定根及其左右分布情况,否则会得到多个二叉树)


第一部分小结

因为树这一节知识结构分布不太一样,每一部分都涉及大量知识点,所以单独小结。
第一部分思维导图


第二部分:线索二叉树

线索二叉树是对二叉链表存储的改进。

  • 传统的二叉链表存储仅能体现一种父子关系,无法直接得到结点在遍历中的前驱或后继。
  • 而每个叶子结点度为1的结点都存在空指针,有效利用这些空指针指向其对应的前驱结点可以加快查找某一结点前驱和后继的速度

为此还需要添加两个标识域,用于标识指针是指向其左/右孩子还是指向其遍历时的前驱/后继

  • ltag { 0 , l c h i l d 域指示结点的左孩子 1 , l c h i l d 域指示遍历时结点的前驱 \begin{cases} 0,lchild域指示结点的左孩子 \\ 1,lchild域指示遍历时结点的前驱 \end{cases} { 0lchild域指示结点的左孩子1lchild域指示遍历时结点的前驱
  • rtag { 0 , r c h i l d 域指示结点的右孩子 1 , r c h i l d 域指示遍历时结点的后继 \begin{cases} 0,rchild域指示结点的右孩子 \\ 1,rchild域指示遍历时结点的后继 \end{cases} { 0rchild域指示结点的右孩子1rchild域指示遍历时结点的后继

例如:构造下列二叉树的中序线索二叉树
中序线索二叉树示意图

第二部分习题

在这里插入图片描述


第三部分:树的存储方式

前面介绍了二叉树的存储结构,树同样也可以采用顺序存储结构和链式存储结构,但无论是哪种存储结构都需要能唯一反映树中各结点之间的逻辑关系。
下面介绍三种表示法,对应了三种存储方式(包含一种或两种存储结构)。

1、双亲表示法

双亲表示法就是采用一组连续空间(即静态链表)来存储每个结点。
采用顺序存储结构,在每个结点中增设一个伪指针(一个结点就是一个结构体对象),指示其双亲结点在数组中的位置下标。

2、孩子表示法

将每个结点的孩子结点,按从左到右的顺序用单链表链接起来( n 个结点就有 n 个孩子链表)。
采用顺序表+链表的结构,结合层次遍历实现该方法。链表的每个结点存储了树中当前结点的孩子节点对应的数组下标,以及下一个链表结点的地址。

3、孩子兄弟表示法

孩子兄弟表示法又叫做二叉树表示法。这种表示法是三者之中最重要的
二叉链表作为树的存储结构。孩子兄弟表示法使每个结点包括三部分内容:

  • 结点值;
  • 左指针指向结点的第一个孩子结点;
  • 右指针指向结点的下一个兄弟结点(沿此域可以找到结点的所有兄弟结点)。

孩子兄弟表示法可以将树转换成二叉树:
树转换二叉树流程图
① 将一棵树的兄弟结点之间相连,然后双亲只保留和第一个孩子结点的连线,从而生成对应的二叉树
② 将对应的二叉树用链表表示,每个结点左侧相连的结点用左指针指向,右侧相连的结点用右指针指向,形成二叉树表示法
③ 为了更加直观反映每个结点左右指针的指向情况,将二叉树链表画成二叉树的形式


第四部分:树和森林

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

  • 树转换为二叉树的步骤已经在上一部分介绍了,而将二叉树转换回树也很简单:
    ① 将兄弟结点重新连回双亲结点(每个左孩子的双亲就是它右侧整条路径上的结点的双亲);
    ② 断开兄弟结点之间的连线(右侧整条路径上各节点之间的连线)。

除此之外,二叉树和森林之间也可以相互转换。(不介绍一般的树和森林直接转换)

  • 森林转换为二叉树比较简单:
    ① 将森林中的每棵树先转换为二叉树,此时各个二叉树的根节点必然是没有右孩子的;
    ② 将后一棵二叉树视为前一棵二叉树的右子树,依次拼接在一起,森林就转换成了一棵二叉树。

  • 反过来执行上述操作,便可以将二叉树转换为森林
    ① 若二叉树非空,将其根节点与根节点的左子树视为第一棵二叉树,与右子树断开;
    ② 对断开后的右子树重复操作①,直到某一结点没有右孩子(一个结点也可单独成树);
    ② 将所得的每棵二叉树都转换回树,就得到了森林。

2、数和森林的遍历

树的遍历:

  • 先根遍历:若树非空,则先访问根结点,再按从左到右的顺序遍历根结点的每棵子树。
    其访问顺序与这棵树相应二叉树的先序遍历顺序相同,即先根遍历所得的序列,与这棵树所转换的二叉树进行先序遍历所得的序列相同
  • 后根遍历:若树非空,则按从左到右的顺序遍历根结点的每棵子树,之后再访问根结点。
    其访问顺序与这棵树相应二叉树的中序遍历顺序相同,即后根遍历所得的序列,与这棵树所转换的二叉树进行中序遍历所得的序列相同

第四部分习题

  1. 已知某二叉树的先序序列和中序序列分别为 ABDEHCFIMGJKL 和 DBHEAIMFCGKLJ,请画出这棵二叉树,并画出二叉树对应的森林。
    序列求二叉树并转换为森林

第五部分:二叉排序树

二叉排序树(BST),也称二叉查找树或者二叉判定树(口诀:左小右大)。原属于第七章查找部分的知识点。

1、二叉排序树的定义

(1)若左子树非空,则左子树上所有结点关键字值均小于根结点的关键字值。
(2)若右子树非空,则右子树上所有结点关键字值均大于根结点的关键字值。
(3)左、右子树本身也分别是一棵二叉排序树(满足上述两点)。

2、二叉排序树的插入

插入操作就是严格遵守“比当前结点小就进入左子树区域,比当前结点大就进入右子树区域——左小右大,直到自身成为叶子结点
请按顺序插入:53 17 78 9 45 65 94 81 88 23,生成的二叉排序树如图:

3、二叉排序树的删除

删除操作的实现过程按 3 种情况来处理:
① 若被删除结点 z 是叶子结点,则直接删除。
② 若结点 z 只有一棵左子树或右子树,则让 z 的子树顶上,替代 z 的位置。
③ 若结点 z 有左、右两棵子树,则先中序遍历得到序列,令 z 在序列中的直接后继(或直接前驱)替代 z,然后从二叉排序树中删去这个直接后继(或直接前驱),此时就转换成了第一或第二种情况。

第①小点例题:删除88;
第②小点例题:删除45;
第③小点例题:删除78。由于中序表达式:9 17 23 45 53 65 78 81 88 94,所以:

  • 78的直接前驱是65,用它替换变为第①小点的类型;
  • 78的直接后继是81,用它替换变为第②小点的类型;

4、二叉排序树的查找

查找过程就是从根结点开始,将给定的值与结点的关键字对比。

  • 若给定值与结点的关键字相等,则查找成功;
  • 若给定值与结点关键字不等,小于结点的关键字就进入当前结点的左子树继续查找,大于结点的关键字就进入当前结点的右子树上查找。

查找所有给定值(给定值一般就是组成树的这些关键字的集合)需要比较的关键字总个数,除以给定值的总个数,所得的结果我们称为平均查找长度:ASL——A:average;S:search;L:len

情况(a) 情况(b)
查找成功/失败的平均查找长度ASL为:
{ A S L 成功 = ( 1 + 2 ∗ 2 + 3 ∗ 4 + 4 ∗ 3 ) / 10 = 2.9 A S L 失败 = ( 3 ∗ 5 + 4 ∗ 6 ) / 11 = 39 / 11 \begin{cases} ASL_{成功}=(1+2*2+3*4+4*3)/10= 2.9 \\ ASL_{失败}=(3*5+4*6)/11 = 39/11 \end{cases} { ASL成功=(1+22+34+43)/10=2.9ASL失败=(35+46)/11=39/11
查找成功/失败的平均查找长度ASL为:
{ A S L 成功 = ( 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 ) / 10 = 2.9 A S L 失败 = ( 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 ∗ 2 ) / 11 = 65 / 11 \begin{cases} ASL_{成功}=(1+2+3+4+5+6+7+8+9+10)/10= 2.9 \\ ASL_{失败}=(1+2+3+4+5+6+7+8+9+10*2)/11 = 65/11 \end{cases} { ASL成功=(1+2+3+4+5+6+7+8+9+10)/10=2.9ASL失败=(1+2+3+4+5+6+7+8+9+102)/11=65/11
给定值全部查找成功的情况下:
第一层1个元素各需要比较1次;
第二层2个元素各需要比较2次;
第三层4个元素各需要比较3次;
第四层3个元素各需要比较4次;
给定值全部查找成功的情况下:
每层1个元素需要比较k次,k为所在层数(深度);
给定值全部查找失败的情况下:
第三层12、53、60共5种失败可能,各需比较3次;
第四层28、40、70共6种失败可能,各需比较4次;
给定值全部查找失败的情况下:
前九层各有1种失败可能,均需比较k次,k为所在层数(深度);
第十层有2种失败可能,均需比较10次;

第五部分习题

  1. 对于下列关键字序列,不可能构成某二叉排序树中一条查找路径的是(A)。
    A.95,22,91,24,94,71(所构成的二叉排序树出现了度为2的分支结点,不可数是查找路径)
    B.92,20,91,34,88,35
    C.21,89,77,29,36,38
    D.12,25,71,68,33,34

  2. 从空树开始,依次插入元素 52,26,14,32,71,60,93,58,24 和 41 后构成了一棵二叉排序树。在该树查找60 要进行比较的次数为(D)。
    A.3(所构成的二叉排序树中,60所在的结点深度为3)
    B.4
    C.5
    D.6

  3. 一棵二叉排序树按先序遍历得到的序列为(50,38,30,45,40,48,70,60,75,80),试画出该二叉排序树,并求出等概率下查找成功和查找失败的平均查找长度。


第二到第五部分小结


第六部分:平衡二叉树

1、平衡二叉树的定义

又称平衡树(AVL):任意结点的左、右子树高度差的绝对值不超过1,且子树都是平衡树。
定义每个结点左子树与右子树的高度差为该结点的平衡因子,平衡因子的值只可能是 -1、0 或 1。叶子结点的平衡因子一定为 0。
考试中一般不会让你计算平衡因子,知道这个概念是为了判断平衡二叉树。

2、最小平衡二叉树公式★

最小平衡二叉树公式:F(n)=F(n-1)+F(n-2)+1,用于计算构建某一高度的平衡二叉树所需结点个数的最小值。其中:

  • n为高度,F(n)为结点数;
  • F(n-1)是左子树的结点总数量,F(n-2)是右子树的结点总数量;
  • 1是根节点。

例如:F(1)=1,F(2)=2,得:F(3)=2+1+1=4;{ F(h) | h = 1,2,3,… } = { 1,2,4,7,12,20,33,… }

【例题】

  1. 含有20个结点的平衡二叉树的最大深度为(C)。
    A.4
    B.5
    C.6( 列出数列:1、2、4、7、12、20,可知 F(6)=20,即最大深度 h=6)
    D.7

  2. 下列二又排序树中,满足平衡二叉树定义的是(B)。

3、平衡二叉树的插入

平衡二叉树实际上就是对二叉排序树引入了新的约束,用来限制二叉排序树的高度,从而加快查找速度(减少平均查找路径)。因此平衡二叉树的插入操作同样遵循“左小右大”的方式,并且还需要时刻注意保证每个结点的平衡因子的绝对值不能超过1

当插入后出现了平衡因子的绝对值大于1的结点,就需要对当前的树做出调整,使它变回平衡二叉树。调整需要遵循两个原则:① 降低高度;② 保持二叉排序树性质。

(1)LL型右旋转
LL型插入过程
(2)RR型左旋转
RR型插入过程
(3)LR型先左旋转后右旋转
LR型插入过程
(4)RL型先右旋转后左旋转
RL型插入过程
关于上面四类情况,有几点需要补充:

  • 这里进行变换的“根节点”并不一定是整棵树的根节点(所以上方打了省略号)。我们所变换的这棵树叫“最小不平衡子树”,这棵子树的根结点是离插入点最近的一个平衡因子的绝对值大于1的结点;
  • 每种类型里的 L 和 R 只取决于“根节点”往下的两段路径的选取,之后的插入路径不影响“最小不平衡子树”的所属类别。也就是说在走过两段路径进入到子树后,还会出现左插入右插入两种情况,即可以将上述四类进一步细分为:LL(L)、LL(R)、RR(R)、RR(L)、LR(L)、LR(R)、RL(L)、RL(R);( 上图展示的就是 LR(R) 和 RL(R) )

4、平衡二叉树的删除

平衡二叉树的删除操作同样与二叉排序树一样,依据删除结点的不同情况要进行不同的变换。如果在变换过程中也破坏了树的“平衡性”,就像插入操作中一样找出“最小不平衡子树”,然后依据从“根节点”到达被删除结点的路径的前两段确定所属类型,再对“最小不平衡子树”进行相应变换。

第六部分习题

  1. 在如下图所示的平衡二叉树中插入关键字 48 后得到一棵新平衡二叉树,在新平衡二叉树中,关键字 37 所在结点的左、右子结点中保存的关键字分别是(C)。
       A.13,48   B.24,48   C.24,53   D.24,90

【分析】本次插入是 RL型,进行相应变换可得 37 的子节点为 24 和 53 。   


第七部分:哈夫曼树

1、哈夫曼树的定义

赋予树的结点权值,所得到的就是带权树

  • 我们将根节点到某一带权结点的路径长度与权值的乘积,称为结点的带权路径长度
  • 所有叶子结点的带权路径长度之和,称为树的带权路径长度——WPL(W:weighted;P:path;L:len)。
  • 带权路径长度最小的二叉树称为哈夫曼树,也称最优二叉树

【经典例题】二叉树有 4 个叶子结点 a、b、c、d,分别带权 7、5、2、4,该二叉树的最小带权路径长度为:

2、哈夫曼树的构造

哈夫曼树的构造在课本有详细描述,这里简单概括并附上口诀:
(1)构造森林全是根;
(2)选用两小造新人;
(3)删除两小添新人;
(4)重复2、3剩单根。

简单概括,就是用单个结点存储权值,每次选择权值最小的两个结点作为左右子节点构造一棵树,然后删除这两个结点并将它们的权值之和存储在根节点中,再把根节点添加进可选结点,然后重新选择两个新的最小结点重复上面步骤,直到可选的结点只剩一个。

  • 这样每次选取最小权值,可以保证权值较大的结点靠近根节点,从而结点的带权路径小。

3、哈夫曼编码

哈夫曼编码的目的是获取最优的前缀编码

  • 前缀编码:在一个编码方案中,任何一个编码都不是其他任何编码的前缀(即最左子串,如“01”就是“010”的前缀);
  • 最优编码:以该编码方案对文件进行压缩编码,能使获得的二进制文件长度最短

哈夫曼编码的实现:(口诀:左邻右里

  • 以字符在文件中出现的次数为作为权值,构建哈夫曼树;
  • 将哈夫曼树的左分支赋予 0,右分支赋予 1,则从根节点到每个叶子结点的路径上,各分支的赋值就构成了一个二进制串,将该二进制串作为叶子结点对应字符的编码。

第七部分习题

  1. 5个字符有如下4种编码方案,不是前缀编码的是(D)。
    A.01,0000,0001,001,1
    B.011,000,001,010,1
    C.000,001,010,011,100
    D.0,100,110,1110,1100(明显110是1100的前缀。当然我们也可以通过画左0右1的二叉树,假设五个字符分别是A、B、C、D、E,画完后发现C是E的双亲结点,不是叶子结点。)

  2. 设给定权集 w = [ 5,7,2,3,6,8,9 ],试构造关于w的一根哈夫曼树,并求其加权路径长度WPL。

  


第六到七部分小结

猜你喜欢

转载自blog.csdn.net/qq_50571974/article/details/126654353
今日推荐