@树和二叉树

本文深入探讨了树和二叉树这两种非线性数据结构的定义、特性及应用,覆盖了树的基本术语、二叉树的特殊形态、遍历算法、存储结构以及哈夫曼树的构建与应用等内容。

第 5 章 树和二叉树
树是一类重要的非线性数据结构,树形结构是以分支关系来定义的层次结构。在客观世界中树形结构广泛存在,并应用于:
•人类社会的族谱、家谱、行政区域划分管理;
•各种社会组织机构;
•在计算机领域中,用树表示源程序的语法结构;
•在操作系统(OS)中,文件系统、目录等组织结构也是用树来表示的。
第 5 章 树和二叉树

树的定义
树:n(n≥0)个结点的有限集合。当n=0时,称为
空树;任意一棵非空树满足以下条件:
⑴有且仅有一个特定的称为根的结点;
⑵当n>1时,除根结点之外的其余结点被分成m (m>0)个互不相交的有限集合T1,T2,… ,Tm,其中每个集合又是一棵树,并称为这个根结点的子树。
树的定义是采用递归方法
树的定义

(a) 一棵树结构 (b)一个非树结构 ©一个非树结构

  1. 树的基本术语

结点的度:结点所拥有的子树的个数。
树的度:树中各结点度的最大值。

  1. 树的基本术语

叶子结点:度为0的结点,也称为终端结点。
分支结点:度不为0的结点,也称为非终端结点。

1. 树的基本术语

孩子、双亲:树中某结点子树的根结点称为这个结点的孩子结点,这个结点称为它孩子结点的双亲结点;兄弟:具有同一个双亲的孩子结点互称为兄弟。

**

1. 树的基本术语

**
路径:如果树的结点序列n1, n2, …, nk有如下关系:结点ni是ni+1的双亲(1<=i<k),则把n1, n2, …, nk称为一条由n1至nk的路径;路径上经过的边的个数称为路径长度。 
树的基本术语
祖先、子孙:在树中,如果有一条路径从结点x到结点y,那么x就称为y的祖先,而y称为x的子孙。

**

1. 树的基本术语

**
结点所在层数:根结点的层数为1;对其余任何结点,若某结点在第k层,则其孩子结点在第k+1层。
树的深度:树中所有结点的最大层数,也称高度。

  1. 树的基本术语

层序编号:将树中结点按照从上层到下层、同层从左到右的次序依次给他们编以从1开始的连续自然数。

树的基本术语
有序树、无序树:如果一棵树中结点的各子树从左到右是有次序的,称这棵树为有序树;反之,称为无序树。

数据结构中讨论的一般都是有序树
树的基本术语
森林:m (m≥0)棵互不相交的树的集合。

树的基本术语
同构:对两棵树,若通过对结点适当地重命名,就可以使这两棵树完全相等(结点对应相等,结点对应关系也相等),则称这两棵树同构。

  • 树结构和线性结构的比较线性结构树结构
  • 第一个数据元素根结点(只有一个)无前驱无双亲 最后一个数据元素叶子结点(可以有多个) 无后继无孩子其它数据元素其它结点
    一个前驱,一个后继一个双亲,多个孩子一对一 一对多 树的抽象数据类型定义 ADT Tree Data
    树是由一个根结点和若干棵子树构成,树中结点具有相同数据类型及层次关系 Operation InitTree
    前置条件:树不存在输入:无功能:初始化一棵树输出:无后置条件:构造一个空树 树的抽象数据类型定义 DestroyTree
    前置条件:树已存在输入:无功能:销毁一棵树输出:无 后置条件:释放该树占用的存储空间 Root
    前置条件:树已存在输入:无功能:求树的根结点输出:树的根结点的信息后置条件:树保持不变 树的抽象数据类型定义 Parent
    前置条件:树已存在输入:结点x 功能:求结点x的双亲输出:结点x的双亲的信息后置条件:树保持不变 Depth
    前置条件:树已存在输入:无功能:求树的深度输出:树的深度后置条件:树保持不变 树的抽象数据类型定义 PreOrder
    前置条件:树已存在输入:无功能:前序遍历树输出:树的前序遍历序列后置条件:树保持不变 PostOrder
    前置条件:树已存在输入:无功能:后序遍历树 输出:树的后序遍历序列后置条件:树保持不变 endADT 树的遍历操作
    树的遍历:从根结点出发,按照某种次序访问树中所有结点,使得每个结点被访问一次且仅被访问一次。如何理解访问?
    抽象操作,可以是对结点进行的各种处理,这里简化为输出结点的数据。 遍历的实质? 树结构(非线性结构)→线性结构。 如何理解次序?
    树通常有前序(根)遍历、后序(根)遍历和层序(次)遍历三种方式。 前序遍历 树的前序遍历操作定义为:若树为空,则空操作返回;否则
    ⑴访问根结点; ⑵按照从左到右的顺序前序遍历根结点的每一棵子树。 前序遍历序列: A B D E H I F C G
  • 后序遍历 树的后序遍历操作定义为:若树为空,则空操作返回;否则 ⑴ 按照从左到右的顺序后序遍历根结点的每一棵子树; ⑵访问根结点。
    后序遍历序列: D H I E F B G C A 层序遍历
    树的层序遍历操作定义为:从树的第一层(即根结点)开始,自上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。 层序遍历序列: A
    B C D E F G H I
    实现树的存储结构,关键是什么? 如何表示树中结点之间的逻辑关系。 什么是存储结构? 数据元素以及数据元素之间的逻辑关系在存储器中的表示。 树中结点之间的逻辑关系是什么? 思考问题的出发点:如何表示结点的双亲和孩子
    双亲表示法
    基本思想:用一维数组来存储树的各个结点(一般按层序存储),数组中的一个元素对应树中的一个结点,包括结点的数据信息以及该结点的双亲在数组中的下标。
    data parent data:存储树中结点的数据信息 parent:存储该结点的双亲在数组中的下标 双亲表示法 data
    parent template struct PNode { T data; //数据域 int
    parent; //指针域,双亲在数组中的下标 } ; 树的双亲表示法实质上是一个静态链表。

双亲表示法
下标 data parent

A -1
B 0
C 0
D 1
E 1
F 1
G 2
H 2
I 4
0
1
2
3
4
5
6
7
8
如何查找双亲结点?时间性能?
data parent firstchild
A -1 1
B 0 3
C 0 6
D 1 -1
E 1 8
F 1 -1
G 2 -1
H 2 -1
I 4 -1
双亲表示法 下标
如何查找孩子结点?时间性能?
data parent rightsib
A -1 -1
B 0 2
C 0 -1
D 1 4
E 1 5
F 1 -1
G 2 7
H 2 -1
I 4 -1
双亲表示法 下标
如何查找兄弟结点?时间性能?

孩子表示法——多重链表表示法
如何确定链表中的结点结构?
链表中的每个结点包括一个数据域和多个指针域,每个指针域指向该结点的一个孩子结点。
方案一:指针域的个数等于树的度
data child1 child2 …… childd
其中:data:数据域,存放该结点的数据信息;
child1~childd:指针域,指向该结点的孩子。

孩子表示法——多重链表表示法
如何确定链表中的结点结构?
链表中的每个结点包括一个数据域和多个指针域,每个指针域指向该结点的一个孩子结点。
方案二:指针域的个数等于该结点的度
data degree child1 child2 …… childd
其中:data:数据域,存放该结点的数据信息; degree:度域,存放该结点的度; child1~childd:指针域,指向该结点的孩子。缺点:结点结构不一致

孩子表示法——孩子链表表示法
如何确定链表中的结点结构?
将结点的所有孩子放在一起,构成线性表。
基本思想:把每个结点的孩子排列起来,看成是一个线性表,且以单链表存储,则n个结点共有 n 个孩子链表。这 n 个单链表共有 n 个头指针,这 n 个头指针又组成了一个线性表,为了便于进行查找采用顺序存储。最后,将存放 n 个头指针的数组和存放n个结点的数组结合起来,构成孩子链表的表头数组。
孩子表示法——孩子链表表示法
孩子结点 struct CTNode
{
child next
int child;
CTNode *next; };
template
表头结点 struct CBNode
data firstchild
{
T data;
CTNode *firstchild;
};

  1. 双亲孩子表示法

  1. 孩子兄弟表示法

结点结构 firstchild data rightsib
data:数据域,存储该结点的数据信息; firstchild:指针域,指向该结点第一个孩子; rightsib:指针域,指向该结点的右兄弟结点。

template
struct TNode
{
T data;
TNode *firstchild, *rightsib; };

  1. 5.3 二叉树的逻辑结构

  2. 研究二叉树的意义? 问题转化:将树转换为二叉树,从而利用二叉树解决树的有关问题。 二叉树的定义
    二叉树是n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
    二叉树的特点: ⑴每个结点最多有两棵子树; ⑵二叉树是有序的,其次序不能任意颠倒。 注意:二叉树和树是两种树结构。 二叉树的基本形态
    Ф 空二叉树 只有一个根结点 根结点只有右子树 根结点只有左子树 根结点同时有左右子树
    具有3个结点的树和具有3个结点的二叉树的形态 二叉树和树是两种树结构。特殊的二叉树 斜树 1 .所有结点都只有左子
    树的二叉树称为左斜树; 2 .所有结点都只有右子树的二叉树称为右斜树; 3.左斜树和右斜树统称为 斜树。

    1. 在斜树中,每一层只有一个结点;斜树的特点:
      2.斜树的结点个数与其深度相同。 特殊的二叉树满二叉树 在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子 都在同一层上。 O 9 10 11 12 13 14 15 满二叉树的特点: 1. 叶子只能出现在最下一层;
    2. 只有度为0和度为2的结点。 特殊的二叉树 满二叉树在同样深度的二叉树中结点个数最多满二叉树在同样深度的二叉树中叶子结点个数最多 特殊的二叉树完全二叉树
      对一棵具有n个结点的二叉树按层序编号,如果编号为i(1≤i≤n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中的位置完全相同。  
      特殊的二叉树 完全二叉树的特点
    3. 叶子结点只能出现在最下 两层,且最下层的叶子结点 H 都集中在二叉树的左部;
    4. 完全二叉树中如果有度为 1的结点,只可能有一个,且该结点只有左孩子。
    5. 深度为k的完全二叉树在 k-1层上一定是满二叉树。 二叉树的基本性质 性质5-1 二叉树的第i层上最多有2i-1个结点(i≥1)。 证明:当i=1时,第1层只有一个根结点,而 2i-1=20 =1,结论显然成立。 假定i=k(1≤k<i)时结论成立,即第k层上至多有
      2k-1个结点,则 i=k+1时,因为第k+1层上的结点是第k层上结点的孩子,而二叉树中每个结点最多有2
      个孩子,故在第k+1层上最大结点个数为第k层上的最大结点个数的二倍,即2×2k-1=2k。结论成立。 二叉树的基本性质 性质5-2
      一棵深度为k的二叉树中,最多有2k-1个结点,最少有k个结点。 证明:由性质1可知,深度为k的二叉树中结点个数最多
      = ∑ (k)(第i层上结点的最大个数 =2) k-1; 每一(i=1)层至少要有一个结点,因此深度为k的二叉树,至少有k个结点。 深度为k且具有2k-1个结点的二叉树一定是满二叉树,深度为k且具有k个结点的二叉树不一定是斜树。 二叉树的基本性质 性质5-3
      在一棵二叉树中,如果叶子结点数为n0,度为2的结点数为n2,则有: n0=n2+1。 证明:设n1为二叉树T中度为1的结点数
      同理:三次树:四次树: ∵ 二叉树中所有结点的度均小于或等于2 ∴ 结点总数n=n +n +n (1)
      除根结点外,其余结点都只有一个 分支进入,设B为分支总数,则n=B+1 (2) 又:分支由度为1和度为2的结点射出, ∴B=n1+2n2
      于是,n=B+1=n1+2n2+1 (3) ∴由式(1)和(3)得 n0=n2+1 n0=1+n2+2n3
      n0=1+n2+2n3+3n4 … K次树: n0=1+n2+2n3+…+(k-1)nk 二叉树的基本性质 性质5-3
      在一棵二叉树中,如果叶子结点数为n0,度为2的结点数为n2,则有: n0=n2+1。
      在有n个结点的满二叉树中,有多少个叶子结点?因为在满二叉树中没有度为1的结点,只有度为0的叶子结点和度为2的分支结点,所以, n= n0
    • n2 n0=n2 + 1 即叶子结点n0=(n + 1)/2 完全二叉树的基本性质 性质5-4 具有n个结点的完全二叉树的深度为log2n
      +1。证明:假设具有n个结点的完全二叉树的深度为k,根据完全二叉树的定义和性质2,有下式成立 2k-1 ≤ n < 2k ———第k-1层 ———第k层 完全二叉树的基本性质 性质5-4 具有n个结点的完全二叉树的深度为log2n
      +1。证明:假设具有n个结点的完全二叉树的深度为k,根据完全二叉树的定义和性质2,有下式成立 2k-1 ≤ n < 2k k所在区间对不等式取对数,有: k-1≤log2n<k ⎣ log2n⎦ log2n ⎣ log2n ⎦ +1 log2n +
      1 即: log2n<k≤log2n+1 由于k是整数,故必有k=⎣ log2n ⎦ +1。
      说明:符号⎣x⎦表示不大于x的最大整数。 完全二叉树的基本性质 性质5-5
      对一棵具有n个结点的完全二叉树中从1开始按层序编号,则对于任意的序号为i(1≤i≤n)的结点(简称为结点i),有:
      (1)如果i>1,则结点i的双亲结点的序号为 i/2;如果 i=1,则结点i是根结点,无双亲结点。
      (2)如果2i≤n,则结点i的左孩子的序号为2i;如果2i>n,则结点i无左孩子。
      (3)如果2i+1≤n,则结点i的右孩子的序号为2i+1;如果2i+1>n,则结点 i无右孩子。 完全二叉树的基本性质
      对一棵具有n个结点的完全二叉树中从1开始按层序编 号,则 结点i的双亲结点为 i/2; 结点i的左孩子为2i;
      结点i的右孩子为2i+1。 性质5表明,在完全二叉树中,结点的层序编号反映了结点之间的逻辑关系。 二叉树的抽象数据类型定义 ADT
      BiTree Data 由一个根结点和两棵互不相交的左右子树构成,结点具有相同数据类型及层次关系 Operation
      InitBiTree 前置条件:无输入:无功能:初始化一棵二叉树输出:无后置条件:构造一个空的二叉树 二叉树的抽象数据类型定义
      DestroyBiTree 前置条件:二叉树已存在输入:无功能:销毁一棵二叉树输出:无 后置条件:释放二叉树占用的存储空间
      InsertL 前置条件:二叉树已存在 输入:数据值x,指针parent 功能:将数据域为x的结点插入到二叉树中,作为结点
      parent的左孩子。如果结点parent原来有左孩子,则将结点parent原来的左孩子作为结点x的左孩子输出:无后置条件:如果插入成功,得到一个新的二叉树
      二叉树的抽象数据类型定义 DeleteL 前置条件:二叉树已存在输入:指针parent
      功能:在二叉树中删除结点parent的左子树输出:无 后置条件:如果删除成功,得到一个新的二叉树 Search
      前置条件:二叉树已存在输入:数据值x 功能:在二叉树中查找数据元素x 输出:指向该元素结点的指针后置条件:二叉树不变
      二叉树的抽象数据类型定义 PreOrder
      前置条件:二叉树已存在输入:无功能:前序遍历二叉树输出:二叉树中结点的一个线性排列后置条件:二叉树不变 InOrder
      前置条件:二叉树已存在输入:无功能:中序遍历二叉树输出:二叉树中结点的一个线性排列后置条件:二叉树不变 二叉树的抽象数据类型定义
      PostOrder 前置条件:二叉树已存在输入:无功能:后序遍历二叉树输出:二叉树中结点的一个线性排列后置条件:二叉树不变
      LevelOrder 前置条件:二叉树已存在输入:无功能:层序遍历二叉树输出:二叉树中结点的一个线性排列后置条件:二叉树不变
      endADT 二叉树的遍历操作 前序遍历中序遍历后序遍历层序遍历
      二叉树的遍历是指从根结点出发,按照某种次序访问二叉树中的所有结点,使得每个结点被访问一 次且仅被访问一次。
      抽象操作,可以是对结点进行的各种处理,这里简化为输出结点的数据。 二叉树遍历操作的结果? 非线性结构线性化考虑二叉树的组成:
      二根结点D 二叉树的遍历方式:叉 左子树L DLR、LDR、LRD、树右子树R DRL、RDL、RLD
      如果限定先左后右,则二叉树遍历方式有三种:前序:DLR 中序:LDR 后序:LRD 层序遍历:按二叉树的层序编号的次序访问各结点。
      二叉树的遍历操作前序(根)遍历 若二叉树为空,则空操作返回;否则: ①访问根结点; ②前序遍历根结点的左子树;
      ③前序遍历根结点的右子树。 前序遍历序列:A B D G C E F 二叉树的遍历操作中序(根)遍历 若二叉树为空,则空操作返回;否则:
      ①中序遍历根结点的左子树; ②访问根结点; ③中序遍历根结点的右子树。 中序遍历序列:D G B A E C F
      二叉树的遍历操作后序(根)遍历 若二叉树为空,则空操作返回;否则: ①后序遍历根结点的左子树; ②后序遍历根结点的右子树。
      ③访问根结点; 后序遍历序列:G D B E F C A 二叉树的遍历操作层序遍历
      二叉树的层次遍历是指从二叉树的第一层(即根结点)开始,从上至下逐层遍历,在同一层中,则按从左到右的顺序对结点逐个访问。 层序遍历序列:A
      B C D E F G 二叉树遍历操作练习 前序遍历结果:- + a * b - c d / e f 中序遍历结果:a + b * c
    • d - e / f 后序遍历结果:a b c d - * + e f / - 二叉树的遍历操作 若已知一棵二叉树的前序(或中序,或后序,或层序)序列,能否唯一确定这棵二叉树呢? 例:已知前序序列为ABC,则可能的二叉树有5种。
      二叉树的遍历操作 若已知一棵二叉树的前序序列和后序序列,能否唯一确定这棵二叉树呢? 例:已知前序遍历序列为ABC,后序遍历序列为
      CBA,则下列二叉树都满足条件。 二叉树的遍历操作 若已知一棵二叉树的前序序列和中序序列,能否唯一确定这棵二叉树呢?怎样确定?
      例如:已知一棵二叉树的前序遍历序列和中序遍历序列分别为ABCDEFGHI 和BCAEDGHFI,如何构造该二叉树呢? 前序:A B C
      D E F G H I 中序:B C A E D G H F I 前序:B C 中序:B C 前序: D E F G H I 中序: E
      D G H F I   前序:F G H I 中序:G H F I 前序: D E F G H I 中序: E D G H F I  
      二叉树的构造操作 已知一棵二叉树的前序序列和中序序列,构造该二叉树的过程如下:
    1. 根据前序序列的第一个元素建立根结点;
    2. 在中序序列中找到该元素,确定根结点的左右子树 的中序序列;
    3. 在前序序列中确定左右子树的前序序列;
    4. 由左子树的前序序列和中序序列建立左子树;
    5. 由右子树的前序序列和中序序列建立右子树。 顺序存储结构 二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置(下标)应能体现结点之间的逻辑关系——父子关系。
      如何利用数组下标来反映结点之间的逻辑关系? 完全二叉树和满二叉树中结点的序号可以唯一地反映出结点之间的逻辑关系。

    完全二叉树的顺序存储
    数组下标 1 2 3 4 5 6 7 8 9 10 A B C D E F G H I J 二叉树的顺序存储 数组下标 1 2 3 4
    5 6 7 8 9 10 11 12 13
    A B C ∧ D F ∧ ∧ ∧ E ∧ ∧ G 一棵斜树的顺序存储会怎样呢?
    一棵二叉树改造后成完全二叉树形态,需增加很多空结点,造成存储空间的浪费。在最坏的情况下,一个深度为k且只有k个结点的右斜树
    确需要2k-1个结点存储空间。 (若k=20 , 则1M的空间) 二叉树的顺序存储结构一般仅存储完全二叉树 二叉链表
    基本思想:令二叉树的每个结点对应一个链表结点,链表结点除了存放与二叉树结点有关的数据信息外,还要设置指示左右孩子的指针。 结点结构:
    lchild data rchild 其中,data:数据域,存放该结点的数据信息; lchild:左指针域,存放指向左孩子的指针;
    rchild:右指针域,存放指向右孩子的指针。 二叉链表 template struct BiNode { T
    data; BiNode *lchild, *rchild; }; 二叉链表 二叉链表 左斜树的二叉链表 二叉链表
    具有n个结点的二叉链表中,有多少个空指针?具有n个结点的二叉链表中,有n+1个空指针。 证明:
    ∵二叉链表中每个结点都包含2个指针域----左、右指针域 ∴在n个结点的二叉链表中,共有2n个指针域 又∵
    n个结点中只有根结点没有指针指向,其余n-1个节点共有n-1个指针指向,即分支个数=n-1 ∴
    2n个指针域中有n-1个指针域非空则有2n-(n-1)=n+1个空指针域 template class BiTree
    { public: BiTree( ){root=NULL;} BiTree(BiNode *root);
    ~BiTree( ); 二叉链表存储结构的类声明 void PreOrder(BiNode *root);
    void InOrder(BiNode *root); void PostOrder(BiNode *root); void
    LevelOrder(BiNode *root); private: BiNode *root; void
    Creat(BiNode *root); void Release(BiNode *root); };
    前序遍历——递归算法 template void BiTree::PreOrder(BiNode
    *root) { if (root NULL) return; else { cout<data; PreOrder(root->lchild); PreOrder(root->rchild); } }
    前序遍历算法的执行轨迹 中序遍历——递归算法 template void BiTree::InOrder
    (BiNode *root) { if (root
    NULL) return; else {
    InOrder(root->lchild); cout<data; InOrder(root->rchild); } }
    后序遍历——递归算法 template void BiTree::PostOrder(BiNode
    *root) { if (root==NULL) return; else { PostOrder(root->lchild); PostOrder(root->rchild); cout<data; } } 层序遍历

    1. 队列Q初始化;
    2. 如果二叉树非空,将根指针入队;
    3. 循环直到队列Q为空
      3.1 q=队列Q的队头元素出队;
      3.2 访问结点q的数据域;
      3.3 若结点q存在左孩子,则将左孩子指针入队;
      3.4 若结点q存在右孩子,则将右孩子指针入队; 层序遍历 template void BiTree::LevelOrder(BiNode root) {const int MaxSize = 100; int
      front = 0; int rear = 0; //采用顺序队列,并假定不会发生上溢 BiNode
      Q[MaxSize];
      BiNode* q; if (rootNULL) return; else{ Q[rear++] = root; while
      (front != rear) { q = Q[front++]; cout<data<<" "; if (q->lchild
      != NULL) Q[rear++] = q->lchild; if (q->rchild != NULL)
      Q[rear++] = q->rchild; } } } 二叉树的建立
      遍历是二叉树各种操作的基础,可以在遍历的过程中进行各种操作,例如建立一棵二叉树。 如何由一种遍历序列生成该二叉树?
      为了建立一棵二叉树,将二叉树中每个结点的空指针引出一个虚结点,其值为一特定值如“#”,以标识其为空,把这样处理后的二叉树称为原二叉树的扩展二叉树。
      为什么如此处理? 二叉树的建立 扩展二叉树的前序遍历序列:A B # D # # C # # 二叉树的建立
      设二叉树中的结点均为一个字符。假设扩展二叉树的前序遍历序列由键盘输入,root为指向根结点的指针,二叉链表的建立过程是:
      首先输入根结点,若输入的是一个“#”字符,则表明该二叉树为空树,即root=NULL;否则输入的字符应该赋给root->data,,之后依次递归建立它的左子树和右子树。 template BiTree ::BiTree(BiNode *root) Creat(root);
      建立二叉树递归算法 {({ ) } template void BiTree
      ::Creat(BiNode *root) cin>>ch; if (ch
      ’# ‘) root=NULL; else
      { root=new BiNode; root->data=ch; Creat(root->lchild);
      Creat(root->rchild); } } template BiTree
      ::BiTree() 建立二叉树递归算法 {root =Creat (); } template
      BiNode* BiTree ::Creat() { BiNode* root; T ch; cin>>ch;
      if (ch==’# ') root=NULL; else { root=new BiNode; root->data=ch;
      root-> lchild=Creat(); root-> rchild=Creat(); } return root; }
      二叉树的销毁 二叉链表是动态存储分配,在析构函数中释放二叉链表的所有结点。 注意:先释放掉某结点的左、右子树才能释放这个结点。
      所以,采用后序遍历,对结点的访问就是释放该结点。 template BiTree::~BiTree(void)
      { Release(root); } 二叉树的销毁递归算法 { template void
      BiTree::Release(BiNode* root) if (root != NULL){
      Release(root->lchild); //释放左子树 Release(root->rchild); //释放右子树 delete
      root; } }
      遍历二叉树是二叉树各种操作的基础,遍历算法中对每个结点的访问操作可以是多种形式及多个操作,根据遍历算法的框架,适当修改访问操作的内容,可以派生出很多关于二叉树的应用算法。
      void InOrder (BiNode *root) { if (rootNULL) return; else {
      InOrder(root->lchild); cout<data; InOrder(root->rchild); } }
      设计算法求二叉树的结点个数。 int n=0; template void Count(BiNode
      *root) //n为全局量并已初始化为0 { if (root) { Count(root->lchild); n++; Count(root->rchild); } } 设计算法按前序次序打印二叉树中的叶子结点。 template
      void PreOrderleaf(BiNode *root) { if (root) { if (!root->lchild
      && !root->rchild) cout<data; PreOrderleaf(root->lchild);
      PreOrderleaf(root->rchild); } } 设计算法求二叉树的深度。 template int
      Depth(BiNode *root) { if (root
      NULL) return 0; else { int
      hl=Depth(root->lchild); int hr=Depth(root ->rchild); return
      hl>hr?hl+1:hr+1; } } 用孩子-兄弟表示法表示树,设计算法求树中结点 x 的第 i 个孩子。
      用孩子-兄弟表示法表示树,设计算法求树中结点 x 的第 i 个孩子。
      三叉链表 三叉链表 在二叉链表的基础上增加了一个指向双亲的指针域。 结点结构 lchild data parent rchild 其中:data、lchild和rchild三个域的含义同二叉链表的结点结构;
      parent域为指向该结点的双亲结点的指针。 三叉链表的静态链表形式 data parent lchild rchild
      A -1 1 2 B 0 3 -1 C 0 4 5 D 1 -1 6 E 2 -1 -1 F 2 -1 -1 G 3 -1 -1 0 1
      2 3 4 5 6 线索链表 如何保存二叉树的某种遍历序列? 中序遍历序列:D G B A E C F 顺序存储 D G B
      A E C F 如果二叉树不改变,如何保存? 线索链表 如何保存二叉树的某种遍历序列? 线索链表
      如何保存二叉树的某种遍历序列?  线索链表 如何保存二叉树的某种遍历序列? 将二叉链表中的空指针域指向其前驱结点和后继结点
      线索:将二叉链表中的空指针域指向前驱结点和后继结点的指针被称为线索; ltag lchild data rchild rtag
      线索化:使二叉链表中结点的空链域存放其前驱或后继信息的过程称为线索化;线索二叉树:加上线索的二叉树称为线索二叉树。线索链表结点结构
      ltag=0: lchild指向该结点的左孩子 1: lchild指向该结点的前驱结点 rtag=0: rchild指向该结点的右孩子
      1: rchild指向该结点的后继结点 ltag lchild data rchild rtag 线索链表结点结构 enum flag
      {Child, Thread}; template struct ThrNode { T data;
      ThrNode *lchild, *rchild; flag ltag, rtag; }; 线索二叉树
      二叉树的遍历方式有4种,故有4种意义下的前驱和后继,相应的有4种线索二叉树: ⑴ 前序线索二叉树 ⑵ 中序线索二叉树 ⑶ 后序线索二叉树
      ⑷ 层序线索二叉树 线索二叉树 前序线索二叉树 前序序列: A B D G C E F 线索二叉树中序线索二叉树 中序序列:D G
      B A E C F 中序线索化前驱和后继都不需要求双亲,但是都不很直接。
      结点p的前驱:(非线索)结点p的左子树中最右下结点结点p的后继:(非线索)结点p的右子树中最左下结点 线索二叉树 后序线索二叉树
      后序序列: G D B E F C A 树和二叉树之间具有对应关系 树和二叉树之间的对应关系树:兄弟关系 二叉树:双亲和右孩子
      树:双亲和长子 二叉树:双亲和左孩子前序遍历 ABEFCDG 树的前序遍历等价于二叉树的前序遍历! 前序遍历 ABEFCDG
      后序遍历 EFBCGDA 树的后序遍历等价于二叉树的中序遍历! 中序遍历 EFBCGDA 森林转换为二叉树
      ⑴将森林中的每棵树转换成二叉树;
      ⑵从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。
        森林的遍历 森林有两种遍历方法: ⑴前序(根)遍历:前序遍历森林即为前序遍历森林中的每一棵树。 ⑵后序(根)遍历:后序遍历森林即为后序遍历森林中的每一棵树。 相关概念 叶子结点的权值:对叶子结点赋予的一个有意义的数值量。
      二叉树的带权路径长度(Weighted Path
      Length):设二叉树具有n个带权值的叶子结点,从根结点到各个叶子结点的路径长度与相应叶子结点权值的乘积 之和。记为: n WPL=
      ∑ w lk k k=1 从根结点到第k个叶子的路径长度第k个叶子的权值; 哈夫曼树(Huffman
      Tree):给定一组具有确定权值的叶子结点,带权路径长度最小的二叉树。
      例:给定4个叶子结点,其权值分别为{2,3,4,7},可以构造出形状不同的多个二叉树。 WPL=32
      WPL=41 WPL=30 哈夫曼树的特点:
    4. 权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。
    5. 只有度为0(叶子结点)和度为2(分支结点)的结点,不存在度为1的结点. WPL=32 WPL=41 WPL=30 哈夫曼算法基本思想: ⑴初始化:由给定的n个权值{w1,w2,…,wn}构造
      n棵只有一个根结点的二叉树,从而得到一个二叉树集合F={T1,T2,…,Tn};
      ⑵ 选取与合并:在F中选取根结点的权值最小的两棵二叉树分别作为左、右子树构造一棵新的二叉树,这棵新二叉树的根结点的权值为其左、右子树根结点的权值之和;
      ⑶ 删除与加入:在F中删除作为左、右子树的两棵二叉树,并将新建立的二叉树加入到F中;
      ⑷重复⑵、⑶两步,当集合F中只剩下一棵二叉树时,这棵二叉树便是哈夫曼树。 W={2,3,4,5} 哈夫曼树的构造过程 第1步:初始化
      第2步:选取与合并 第3步:删除与加入 W={2,3,4,5} 哈夫曼树的构造过程

    重复第2步

    重复第3步
    W={2,3,4,5} 哈夫曼树的构造过程

    重复第2步
    重复第3步 哈夫曼算法的存储结构

    1. 设置一个数组huffTree[2n struct element { int weight;
      -1]int lchild, rchild, parent;保存哈夫曼树中各点的 }; 信息,数组元素的结点结构。 weight lchild rchild parent 其中:weight:权值域,保存该结点的权值;
      lchild:指针域,结点的左孩子结点在数组中的下标; rchild:指针域,结点的右孩子结点在数组中的下标;
      parent:指针域,该结点的双亲结点在数组中的下标。 伪代码
    2. 数组huffTree初始化,所有元素结点的双亲、左右孩子都置为-1;
    3. 数组huffTree的前n个元素的权值置给定值w[n];
    4. 进行n-1次合并
      3.1 在二叉树集合中选取两个权值最小的根结点,其下标分别为i1, i2;
      3.2 将二叉树i1、i2合并为一棵新的二叉树k;

    weight parent lchild rchild
    2 -1 -1 -1
    4 -1 -1 -1
    5 -1 -1 -1
    3 -1 -1 -1
    -1 -1 -1
    -1 -1 -1
    -1 -1 -1
    0
    1
    2
    3
    4
    5
    6
    初态

4
5
6 5
6 weight parent lchild rchild
2 4-1
-1 -1
4 5-1
-1 -1
5 6-1
-1 -1
3 4-1
-1 -1
5 5-1
0 -1
3 -1

9 6-1
1 -1
4-1

14 -1 2 -1
5-1

0
1
2

void HuffmanTree(element huffTree[ ], int w[ ], int n )
{ for (i=0; i<2n-1; i++) { huffTree [i].parent= -1; huffTree [i].lchild= -1; huffTree [i].rchild= -1;
}
for (i=0; i<n; i++)
huffTree [i].weight=w[i];
for (k=n; k<2
n-1; k++) { Select(huffTree, i1, i2);
huffTree[k].weight=huffTree[i1].weight+huffTree[i2].weight; huffTree[i1].parent=k; huffTree[i2].parent=k; huffTree[k].lchild=i1; huffTree[k].rchild=i2;
}
}

  1. 哈夫曼树应用——哈夫曼编码 编码:给每一个对象标记一个二进制位串来表示一组对象。 例:ASCII,指令系统
    等长编码:表示一组对象的二进制位串的长度相等。不等长编码:表示一组对象的二进制位串的长度不相等。 等长编码什么情况下
    不等长编码什么情况空间效率高?下空间效率高? 哈夫曼树应用——哈夫曼编码 前缀编码:一组编码中任一编码都不是其它任何一个编码的前缀。
    前缀编码保证了在解码时不会有多种可能。 例:一组字符{A, B, C, D, E, F, G}出现的频率分别是
    {9, 11, 5, 7, 8, 2, 3},设计最经济的编码方案。 哈夫曼树应用——哈夫曼编码

编码方案:
A:00
B:10
C:010
D:110
E:111
F:0110
G:0111

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值