Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 174

第5章 树和二叉树

教学内容
 5.1 树和二叉树的定义
 5.2 案例引入
 5.3 树和二叉树的抽象数据类型定义
 5.4 二叉树的性质和存储结构
 5.5 遍历二叉树和线索二叉树
 5.6 树和森林
 5.7 哈夫曼树及其应用
 5.8 案例分析与实现
 5.9 小结
教学目标
 掌握二叉树的基本概念、性质和存储结构
 熟练掌握二叉树的前、中、后序遍历方法
 了解线索化二叉树的思想
 熟练掌握:哈夫曼树的实现方法、构造哈夫
曼编码的方法
 掌握:森林与二叉树的转换,树的遍历方法
5.1 树和二叉树的定义
 5.1.1 树的定义
 5.1.2 树的基本术语
 5.1.3 二叉树的定义
5.1.1 树的定义
定义 树是由 n(n>=0) 个结点组成的有限
集, n=0 时为空树,且对于非空树:
有且仅有一个特定的称为根的结点;
当 n>1 时,其余结点可分为 m(m>0) 个互不相交
的有限集 T1,T2,…,Tm ,其中每一个集合本身又是
一棵树,称为根的子树 (subtree) 。
 这里使用了递归定义。树中的每个结点都是
某一子树的根。
结点包含数据项和指向其它结点的分支信息。
5.1.1 树的定义
 通常将树根画在顶层,再逐层画出子树,这
与自然界生长的树正好相反。

只有根结点的树

有子树的树 根
A
A

B C D

E F G H I J
子树
K L M
5.1.1 树的定义
例:

A A A

B C B C B E C

D E F G D F G D F G

H I
(a) 一棵树结构 (b) 一个非树结构 (c
一个非树结构
5.1.1 树的定义
树的应用举例——文件结构

My Computer

C: D: E: etc
…… ……

WINDOWS …… Program Files Music …… Picture

…… …… …… ……
5.1.1 树的定义
树的表示法有几种:
图形表示法
嵌套集合表示法
广义表表示法 这些表示法的示意图
参见教材 P112
凹入 ( 目录 ) 表示法
5.1.1 树的定义
图形表示法: 河南大学 根

计算机学院 数学学院 物理学院 …



……

教师 学生 其他人员

子树
2016 级 2017 级2018 级 2019 级

叶子
5.1.1 树的定义
树的表示法
A
广义表表示法
B C D

E F G H I J

K L M

( A ( B ( E ( K, L ), F ), C ( G ), D ( H ( M ), I, J ) )
根作为由子树森林组成的表的名字写在表的左边
5.1.2 树的基本术语
 基本术语
结点:一个数据元素 + 若干指向子树的分支
结点拥有的子树数(或分支数)称为结点的度。
 结点 A 的度是 3 , C 的是 1 , G 的是零。度为零的结
点称为叶子结点 / 终端结点。 K , L , F , G , M ,
I , J 是叶子结点。度不为零的结点称为分支结点 / 非
终端结点。
结点的子树的根称为该结点的孩子,该结点称为
孩子的双亲
 D 的孩子是 H , I 和 J ; D 的双亲是 A 。
同一个双亲的孩子是兄弟。
 H , I 和 J 是兄弟。
双亲在同一层的结点称为堂兄弟
5.1.2 树的基本术语
 基本术语
结点的祖先是从根到该结点所经过的所有结点。
 M 的祖先是 A 、 D 和 H 。
结点的子孙是该结点下层子树中的任意结点。
 B 的子孙为 E 、 F 、 K 和 L 。
结点的层次
 从根到该结点的层数(根结点为第一层)。
树的深度
 指所有结点最大的层数( Max{ 各结点的层数 } )。
树的度
 所有结点度的最大值( Max{ 各结点的度 } )。
 上图中树的度为 3
5.1.2 树的基本术语
 基本术语
有序树
 结点各子树从左至右有序,不能互换(左为第一)
 家谱
无序树
 结点各子树可互换位置
 机构
森林
 指 m 棵互不相交的树的集合
任何一棵非空树是一个二元组 Tree
= ( root , F )
 root 被称为根结点, F 被称为子树森林
树结构和线性结构的比较
线性结构 树结构
第一个数据元素 根结点(只有一个)
无前驱 无双亲
最后一个数据元素 叶子结点 ( 可以有多
个)
无后继 无孩子
其它数据元素 其它结点
一个前驱 , 一个后 一个双亲 , 多个孩子
继 一对一
一对多
5.1.3 二叉树的定义
 二叉树的概念
n (n≥0) 个结点的有限集合。当 n=0 时称为空
树。
在一棵非空树 T 中:
 有一个特定的称为根的结点;
 除根结点之外的其余结点被分成两个互不相交的有限
集合 T1 和 T2 ,且 T1 和 T2 都是二叉树,并分别称之为
根的左子树和右子树。
二叉树或为空树;或是由一个
根结点加上两棵分别称为左子树和右
子树的、互不相交的二叉树组成。
右子树
根结点 A
B E
C F
D G
左子树
H K
5.1.3 二叉树的定义
 二叉树的基本特点
结点的度小于等于 2
二叉树的子树有左右之分,其次序不能颠倒

二叉树的五种不同形态
5.1.3 二叉树的定义
 练习
具有 3 个结点的二叉树可能有几种不同形态?普
通树呢? 5 种 /2 种
5.1.3 二叉树的定义
 为何要重点研究每结点最多只有两个 “叉”
的树?
二叉树的结构最简单,规律性最强;
可以证明,所有树都能转为唯一对应的二叉树,
不失一般性。
普通树(多叉树)若不转化为二叉树,则运算很
难实现。
5.2 案例引入
 案例 5.1 :数据压缩问题
将数据文件转换成由 0 、 1 组成的
二进制串,称之为编码。
( a )等长编码方案 ( b )不等长编码方案 1 ( c )不等长
编码方案 2
字符 编码 字符 编码 字符 编码
a 00 a 0 a 0
b 01 b 10 b 01
c 10 c 110 c 010
d 11 d 111 d 111
5.3 树和二叉树的抽象数据类型定义
 二叉树的抽象数据类型定义( P115 )
ADT BinaryTree{
数据对象 D: D 是具有相同特性的数据元素的集合。
数据关系 R:  
5.3 树和二叉树的抽象数据类型定义
CreateBiTree(&T,definition)
初始条件; definition 给出二叉树 T 的定义。
操作结果:按 definition 构造二叉树 T 。

PreOrderTraverse(T)
初始条件:二叉树 T 存在。
操作结果:先序遍历 T ,对每个结点访问一次。
InOrderTraverse(T)
初始条件:二叉树 T 存在。
操作结果:中序遍历 T ,对每个结点访问一次。
PostOrderTraverse(T)
初始条件:二叉树 T 存在。
操作结果:后序遍历 T ,对每个结点访问一次。
5.4 二叉树的性质和存储结构
 5.4.1 二叉树的性质
 5.4.2 二叉树的存储结构
5.4.1 二叉树的性质
 二叉树的性质
性质 1
 在二叉树的第 i 层上至多有 2i - 1 个结点
 第 i 层上至少有 1 个结点
性质 2
 深度为 k 的二叉树至多有 2k-1 个结点
 深度为 k 的二叉树至少有 k 个结点
性质 3
 对于任何一棵二叉树 T ,如果其叶子结点数为 n0 ,度
为 2 的结点数为 n2 ,则 n0=n2+1 。
5.4.1 二叉树的性质
1 1

2 3 2 3

4 5 6 7 4 5 6 7

8 9 10 11 12 8 9 10 11 12

B = n-1 B = 2n2+n1
n=2n2+n1+1 = n2+n1+n0
n2+1 = n0
5.4.1 二叉树的性质
 特殊形态的二叉树 只有最后一层叶子不满,且
满二叉树 叶子全部集中在左边

 一棵深度为 k 且有 2k -1 个结点的二叉树。
完全二叉树
 深度为 k 的,有 n 个结点的二叉树,当且仅当其每一
个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结
点一一对应。
1 1

2 3 2 3

4 5 6 7 4 5 6 7

8 9 10 11 12 13 14 15 8 9 10 11 12
5.4.1 二叉树的性质 1

2 3
 满二叉树的特点
4 5 6 7
 叶子只能出现在最下一层
 只有度为 0 和度为 2 的结点 8 9 10 11 12 13 14 15

 在同样深度的二叉树中叶子结点(结点)个数最多
 完全二叉树的特点
 叶子结点只能出现在最下两层,且最下层的叶子结点都集
中在二叉树的左部;
 完全二叉树中如果有度为 1 的结点,只可能有一个,且该
结点只有左孩子。 1

 深度为 k 的完全二叉树在 k-1 层上


2 3
一定是满二叉树。
4 5 6 7

8 9 10 11 12
5.4.1 二叉树的性质
 满二叉树和完全二叉树的区别
满二叉树是叶子一个也不少的树,而完全二叉树
虽然前 n-1 层是满的,但最底层却允许在右边缺
少连续若干个结点。满二叉树是完全二叉树的一
个特例。

完全二叉树
5.4.1 二叉树的性质
 性质 4
具有 n 个结点的完全二叉树的深度为 log 2 n  +1

。 1
k 1 k
2  n  2
2 3

4 5 6 7 k-1 2k 1  1

8 9 10 11 12 k层 2k  1
k 1
2  n  k  log 2n  1
n n  2k  k  log 2n
log2n   log 2n  log 2n   1
5.4.1 二叉树的性质
 性质 5
对于完全二叉树,若从上至下、从左至右编号,
则编号为 i 的结点,其左孩子编号必为 2i ,右孩
子编号必为 2i + 1 ,双亲的编号必为 i/2 。

性质 5 表明,在完全二叉树中,结点的层序编号反
映了结点之间的逻辑关系。
课堂练习
一棵完全二叉树有 5000 个结点,则其叶子结点
的个数是 2500 。
5.4.1 二叉树的性质
 树T中各结点的度的最大值称为树T的 。
A. 高度 B. 层次 C. 深度 D. 度
 深度为 k 的二叉树的结点总数,最多为 个。
A.2k-1 B. log2k C. 2k - 1 D.2k
 深度为 9 的二叉树中至少有 个结点。
A. 29 B. 28 C. 9 D. 29-1
 一棵完全二叉树具有 1000 个结点,则它有 500 个叶子
结点,有 499 1
个度为 2 的结点,有 个结点
只有非空左子树,有
0 个结点只有非空右子树。
5.4.2 二叉树的存储结构
 二叉树的存储结构
顺序存储结构

链式存储结构
5.4.2 二叉树的存储结构
 二叉树的顺序存储
实现:按满二叉树的结点层次编号,依次存
放二叉树中的数据元素
特点:
 结点间关系蕴含在其存储位置中
 浪费空间,适于存满二叉树和完全二叉树
5.4.2 二叉树的存储结构
 二叉树的顺序存储
0 1 2 3 4 5 6 7 8 9 10
a b c d e 0 0 0 0 f g

#define MAX_SIZE 100


a typedef TElemType SqBiTree[MAX_SIZE];
SqBiTree bt;
b c

d e

f g

单支树
5.4.2 二叉树的存储结构
 二叉树的链式存储结构
用链表来表示一棵二叉树。链表中每个结点由三
个域组成,除了数据域外,还有两个指针域,分
别用来给出该结点的左孩子和右孩子所在结点的
typedef struct BiTNode
存储地址。 { TElemType data;
struct BiTNode *lchild, *rchild;
lchild data rchild } BiTNode , *BiTree;
A

B
data
C D

E F
lchild rchild
G
练习
 画出该二叉树的二叉链表
5.4.2 二叉树的存储结构
 三叉链表
typedef struct TriTNode lchild data parent rchild
{ datatype data;
struct TriTNode *lchild, *rchild, *parent;
}TriTree;
A ^ ^
A

B B

C D D
^ C ^
E F
^ E ^ F ^
G

^ G ^
5.5 遍历二叉树与线索二叉树
 二叉树的遍历
遍历定义
 指以一定的次序访问二叉树中的每个结点,并且每个
结点仅被访问一次。
 “ 访问”的含义:输出结点的信息
遍历用途
 是树结构插入、删除、修改、查找和排序运算的前
提,是二叉树一切运算的基础和核心。
一次完整的遍历可产生树中结点的一个线性序列
5.5.1 遍历二叉树
 遍历规则
二叉树由根、左子树、右子树构成
D 、 L 、 R 的组合定义了六种可能的遍历方
案:
 LDR, LRD, DLR, DRL, RDL, RLD
若限定先左后右,则有三种实现方案:
 DLR LDR LRD
先序遍历 中序遍历
后序遍历
ROOT

LCHILD RCHILD
先左后右的遍历算法
访问根结点、遍历左子树、遍历右子树

先(根)序的遍历算法

中(根)序的遍历算法

左 右
子树 子树
后(根)序的遍历算法
5.5.1 遍历二叉树
 先序遍历 : 先根再左再右

根结点 左子树 右子树


先序: A BDG CEF
5.5.1 遍历二叉树
 中序遍历 : 先左再根再右

中序: DGB A ECF 左子树 根结点 右子树


5.5.1 遍历二叉树
 后序遍历 : 先左再右再根

后序: GDB EFC A 左子树 右子树 根结点


5.5.1 遍历二叉树
例 1
先序遍历:A B D E C
中序遍历:D B E A C
后序遍历:D E B C A

口诀:
DLR— 先序遍历,即先根再左再右
LDR— 中序遍历,即先左再根再右
LRD— 后序遍历,即先左再右再根
5.5.1 遍历二叉树
例 2 + 先序遍历
+**/ABCDE
前缀表示
* E
中序遍历
* D A/B*C*D+E
中缀表示

/ C 后序遍历
AB/C*D*E+
A B 后缀表示

层序遍历二叉树,是从根结点开 层序遍历
始遍历,按层次次序“自上而下, +*E*D/CAB
从左至右”访问树中的各结点。
5.5.1 遍历二叉树
例 3 先序序列:
A ABCDEFGHK

B E 中序序列:
C F BDCAEHGKF

D G
后序序列:
H K DCBHKGFEA
6.3 遍历二叉树与线索二叉树
 练习 写出二叉树遍历的次序

先序: ADEFGBC
中序: DFEGABC
后序: FGEDCBA
遍历的算法实现-先序遍历

若二叉树为空,则空操作 D L R
否则
访问根结点 (D) A
先序遍历左子树 (L) D L R D L R
先序遍历右子树 (R)
B C

>

>
>
A
D L R

B C
D

>
>
D
先序遍历序列: A B D C
先序遍历算法
void PreOrderTraverse(BiTree T){
if(T) {
cout<<T->data; // 访问根结点
PreOrderTraverse(T->lchild); // 递归遍历左子树
PreOrderTraverse(T->rchild); // 递归遍历右子树
}
}
void PreOrderTraverse(BiTree T){ 先序序列: ABDC A
if(T) {
cout<<T->data;
PreOrderTraverse(T->lchild); 左是空返回 B C
PreOrderTraverse(T->rchild); } 左是空返回
} 右是空返回
T

>
D
返回
左是空返回
T B
T D 右是空返回
T A printf(B); T

>
主程序 printf(D);
printf(A); pre(T L);
pre(T 返回
pre(T L); pre(T R);
L);
pre(T R); T

>
Pre( T )
pre(T R);
T C 返回
T

>
printf(C);
pre(T L); 返回
pre(T R); T
北京林业大学信息学院 返回
9/11/23
>
遍历的算法实现-中序遍历

若二叉树为空,则空操作 L D R
否则 :
中序遍历左子树 (L) A
访问根结点 (D) L D R L D R
中序遍历右子树 (R)

>
A B C

>

>
L D R

B C
D

>
>
D
中序遍历序列: B D A
C
中序遍历算法
void InOrderTraverse(BiTree T){
if(T) {
InOrderTraverse(T->lchild); // 递归遍历左子树
cout<<T->data; // 访问根结点
InOrderTraverse(T->rchild); // 递归遍历右子树
}
}
遍历的算法实现-后序遍历
若二叉树为空,则空操作 L R D
否则
后序遍历左子树 (L)
后序遍历右子树 (R) A
访问根结点 (D) L R D L R D

A
B C

>
>
L R D
B C

D
>
D
>

后序遍历序列: D B C
A
后序遍历算法
void PostOrderTraverse(BiTree T){
if(T) {
PostOrderTraverse(T->lchild); // 递归遍历左子树
PostOrderTraverse(T->rchild); // 递归遍历右子树
cout<<T->data; // 访问根结点
}
}
遍历算法的分析 void InOrderTraverse(BiTree T){
if(T) {
InOrderTraverse(T->lchild);
cout<<T->data;
void PreOrderTraverse(BiTree T){
InOrderTraverse(T->rchild);
if(T) {
}
cout<<T->data;
}
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
} void PostOrderTraverse(BiTree T){
} if(T) {
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
cout<<T->data;
}
}
遍历算法的分析
如果去掉输出语句,从递归的角度看,三种算法的访
问路径是相同的,只是访问结点的时机不同。

从虚线的出发点到终点的路径
A 上,每个结点经过 3 次。

B C 第 1 次经过时访问=先序遍历
第 2 次经过时访问=中序遍历
第 3 次经过时访问=后序遍历
D E
二叉树遍历的时间效率和空间效率
F G
时间效率 :O(n) // 每个结点只访问一次
空间效率 :O(n) // 栈占用的最大辅助空

(精确值:树深为 k 的递归遍历需要
k+1 个辅助单元!)
二叉树遍历算法的非递归实现
中序遍历二叉树的非递归算法

D
B
E
CA
中序遍历二叉树的非递归算法
 算法步骤
初始化一个空栈 S ,指针 p 指向根结点
声明指针变量 q ,用来存放栈顶弹出的元素
当 p 非空或栈 S 非空时,循环执行以下操作:
 如果 p 非空,则将 p 进栈, p 指向该结点的左孩子
 如果 p 为空,则弹出栈顶元素并访问,将 p 指向该结
点的右孩子。
中序遍历二叉树的非递归算法
 算法描述
void InOrderTraverse ( Bitree T)
{
InitStack( S );
p=T;
BiTree q;
while ( p || !StackEmpty(S) )
{
if ( p){
Push( S, p );
p=p->lchild );// 根指针进栈,遍历左子树
}
else{
Pop( S, q ); // 退栈
cout<<q->data;// 访问根结点
p=q->rchild;// 遍历右子树
}
}
}
二叉树遍历算法的应用举例
 例 1 创建一棵二叉树

 例 2 计算二叉树结点总数

 例 3 计算二叉树中叶子结点个数

 例 4 计算二叉树的深度
二叉树遍历算法的应用举例
 例 1 创建一棵二叉树
 思路:用先序遍历算法创建二叉树
 已知先序序列为:
A B C # # D E # G # # F # # # 。
 先序遍历的顺序建立二叉链表
 算法 5.3 ( P126 )
void CreateBiTree(BiTree &T){
// 按先序次序输入二叉树中结点的值(一个字符),创建二叉链表表示的二叉树
T
char ch;
cin >> ch;
if(ch=='#') T=NULL; // 递归结束,建空树
else
{
T=new BiTNode;
T->data=ch; // 生成根结点
CreateBiTree(T->lchild); // 递归创建左子树
CreateBiTree(T->rchild); // 递归创建右子树
} }
二叉树遍历算法的应用举例
 例 2 计算二叉树结点总数
如果是空树,则结点个数为 0 ;
否则,结点个数为左子树的结点个数 + 右子树的
结点个数 +1 。
算法 5.6  
int NodeCount(BiTree T){
if(T == NULL )
return 0;
else
return NodeCount(T->lchild)+NodeCount(T-
>rchild)+1;
二叉树遍历算法的应用举例
 例 3 计算二叉树中叶子结点个数
如果是空树,则叶子结点个数为 0 ;
如果根节点左右子树为空,则叶子结点数为 1 ;
否则,为左子树的叶子结点个数 + 右子树的叶子
结点个数。
int LeafCount(BiTree T){
if(T==NULL) // 如果是空树返回 0
return 0;
if (T->lchild == NULL && T->rchild == NULL)
return 1; // 如果是叶子结点返回 1
else return LeafCount(T->lchild) + LeafCount(T-
>rchild);
}
二叉树遍历算法的应用举例
 例 4 计算二叉树的深度
如果是空树,则深度为 0 ;
否则,递归计算左子树的深度记为 m ,递归计算
右子树的深度记为 n ,二叉树的深度则为 m 与 n
的较大者加 1 。int Depth(BiTree T)
{
if (T==NULL) return 0;
else
{
m=Depth(T->lchild);
n=Depth(T->rchild);
if (m>=n) return m+1;
else return n+1;
}
}
特别讨论:若已知先序 / 后序遍历结果和中序遍历结果,
能否“恢复”出二叉树?

例:已知一棵二叉树的中序序列和后序序列分别是 BDCEAFHG
和 DECBHGFA ,请画出这棵二叉树。
分析:
① 由后序遍历特征,根结点必在后序序列尾部(即 A );
② 由中序遍历特征,根结点必在其中间,而且其左部必全部是

左子树子孙(即 BDCE ),其右部必全部是右子树子孙(即


FHG );
③ 继而,根据后序中的 DECB 子树可确定 B 为 A 的左孩子,根

HGF 子串可确定 F 为 A 的右孩子;以此类推。
B D C EA
中序遍历: B A FF H G

后序遍历: D E C BB H GFF A

A
B F
C G
D E H
(BDCE) ( F(H G
H)
(DCE) G)
若已知一棵二叉树的先序序列和中序序列,
能否唯一确定这棵二叉树呢?怎样确定?
例如 : a b c d e f g 先序序列
c b d a e g f 中序序列

a
b ^ e

^ c ^ ^d ^ f ^

g
^ ^
若已知一棵二叉树的先序序列和后序序列,能否
唯一确定这棵二叉树呢?

例:已知先序遍历序列为 ABC ,后序遍历序列


为 CBA ,如何构造二叉树呢 ?

A A

B B

C C
重要结论
 若二叉树中各结点的值均不相同,则:
由二叉树的先序序列和中序序列,或由其后序序
列和中序序列均能唯一地确定一棵二叉树,
但由先序序列和后序序列却不一定能唯一地确定
一棵二叉树。
练习
已知一棵二叉树结点的先序序列和中序序列分别为:

先序序列: 18 14 7 3 11 22 35 27
中序序列: 3 7 11 14 18 22 27 35
给出该二叉树的树形表示(画出该二叉树)。
18

14 22

7 35

3 11 27
讨论:用二叉链表( l_child, r_child )存储包含 n 个结点
的二叉树,结点的指针域中有多少个空指针? n+1
分析:用二叉链表存储包含 n 个结点的二叉树,必有 2n
个链域(见二叉链表数据类型说明)。
除根结点外,二叉树中每一个结点有且仅有一个双亲
(直接前驱),所以只会有 n-1 个链域存放指向非空子女结点
(即直接后继)的指针。

所以, 空指针数目= 2n-(n-1)=n+1 个。


用二叉链表( l_child, r_child )存储包含 n 个
结点的
二叉树,结点的指针域中有 n+1 个空指针

思考:二叉链表空间效率这么低,能否利用这些空闲区存放
有用的信息或线索?
—— 我们可以用它来存放当前结点在某种遍历序列下的直接前
驱和后继等线索,,以提高遍历效率。

线索二叉树
5.5.2 线索二叉树
 对于普通二叉树的某个结点,能够直接得到
的只有它的左右孩子信息,而该结点的直接
前驱和直接后继必须在遍历过程中获得。
 若将遍历后对应的有关前驱和后继预存起
来,则从第一个结点开始就能很快“顺藤摸
瓜”而遍历整个树。
例如对于中序遍历结果: C B E G D F A G
 除 C 、 G 外,显然每个结点具有唯一前驱和唯一后
继!
5.5.2 线索二叉树
 如何保存前驱和后继信息?
增加两个域: fwd 和 bwd ;
两种解决方法
利用空链域( n+1 个空链域)
5.5.2 线索二叉树
 线索二叉树的构造
线索二叉树的结点结构如下:
lchild LTag data RTag rchild
 LTag 若 LTag=0, lchild 域指向左孩子;
若 LTag=1, lchild 域指向其直接前驱
( 线索 ) 。
 RTag 若 RTag=0, rchild 域指向右孩子;
若 RTag=1, rchild 域指向其直接后继
( 线索 ) 。
线索二叉树的结点定义
typedef struct BiThrNode{
TElemType data;
struct BiThrNode *lchild,*rchild;
unsigned char LTag,RTag;
5.5.2 线索二叉树
 线索二叉树的相关术语
线索
 指向结点前驱或后继的指针
线索链表
 以五域线索结点构成的二叉链表
线索二叉树
 加上线索的二叉树
线索化
 对二叉树以某种次序遍历使其变为线索二叉树的过程
5.5.2 线索二叉树
 例:某先序遍历结果如下表所示,请画出对
应的二叉树。
(多带了两个标志!)
LTag 0 0 1 1 1 1 0 1 0 1
data A G E I D J H C F B
RTag 0 0 0 1 0 1 0 1 1 1
A

G H
E D C F

I J B
先序线索二叉树
LTag=0, lchild 域指向左 lchild LTag data RTag rchild
孩子
LTag=1, lchild 域指向其 T
前驱
0 A0
RTag=0, rchild 域指向右
孩子 A
RTag=1, rchild 域指向其
1 B 0 0 D1
后继 B D

C E 1 C 1 1 E 1 ^

先序序列: ABCDE
中序线索二叉树
lchild LTag data RTag rchild
T

A 0 A0

B D
^ 1 B0 0 D1 ^
C E

1 C 1 1 E 1

中序序列: BCAED
后序线索二叉树
lchild LTag data RTag rchild
T

0 A0

A
1 B 0 0 D1
B D

C E ^ 1 C 1 1 E 1

后序序列: CBEDA
练习
画出与二叉树对应的中序线索二叉树

28

NULL 25 33 NULL

40 60 08 54

55
因为中序遍历序列是: 55 40 25 60 28 08
33 54
对应线索树应当按此规律连线,即在原二叉树中添
加虚线。
练习
画出以下二叉树对应的中序线索二叉树。

该二叉树中序遍历结果为 : H, D, I, B, E, A, F, C, G

root A

为避免悬空
悬空?
态,应增设 B C
一个头结点

悬空? D E F G

H I
练习
对应的中序线索二叉树存储结构如图所示:
注:此图中序遍历结果为 : H, D, I, B, E, A, F, C, G

root 0 -- 1

0 A 0

0 B 0 0 C 0

0 D 0 1 E 1 1 F 1 1 G 1

1 H 1 1 I 1
5.5.2 线索二叉树
 构造线索二叉树
在遍历过程中修改空指针:
 将空的 lchild 改为结点的直接前驱;
 将空的 rchild 改为结点的直接后继。
非空指针呢?
 仍然指向孩子结点
5.5.2 线索二叉树
 构造线索二叉树 : 建立线索链表
分析:建立线索链表,实质上就是将二叉链表中的空
指针改为指向前驱或后继的线索,而前驱或后继的信
息只有在遍历该二叉树时才能得到。

建立二叉链表

遍历二叉树,将空指针改为线索
以结点 p 为根的子树中序线索化 ( 算法 5.7, 见
P130 )
目的:在中序遍历二叉树时修改空指针,添加前驱或后继。
注解:为方便添加结点的前驱或后继,需要设置两个指针:
p 指针→当前结点指针; pre 指针→前驱结点指针。
技巧:当结点 p 的左域为空时,改写它的左域(装入前驱
pre ),而其右域留给它的后继结点改写。
此外,当前结点的指针 p 应当送到前驱结点的空
右域中。
若 p->lchild = NULL, 则 {p->LTag=1;p->lchild =
pre;}
//p 的前驱结点指针 pre 存入其
左空域
若 pre->rchild = NULL, 则 {pre->RTag = 1;pre-
>rchild=p;}
//p 存入其前驱结点 pre 的右空域
5.5.2 线索二叉树
中序线索链表 头指针
已经建立起二叉链表
的建立过程
0 A 0

0 B ∧ 0 0 C 0

0 ∧ D 0 0 ∧ E ∧ 0 0 ∧ F ∧ 0

0 ∧ G ∧ 0
5.5.2 线索二叉树
中序线索链表 头指针
中序遍历二叉链表
的建立过程 p 为正在访问的结点
0 A 0 pre 为刚访问的结点

0 B ∧ 0 0 C 0

10 ∧ D 0 0 ∧ E ∧ 0 0 ∧ F ∧ 0

p
0 ∧ G ∧ 0
5.5.2 线索二叉树
中序线索链表 头指针
中序遍历二叉链表
的建立过程 p 为正在访问的结点
0 A 0 pre 为刚访问的结点

0 B ∧ 0 0 C 0

10 ∧ D 0 0 ∧ E ∧ 0 0 ∧ F ∧ 0

pre
01 ∧ G ∧ 0

p
5.5.2 线索二叉树
头指针
中序线索链表 中序遍历二叉链表
的建立过程 p 为正在访问的结点
0 A 0
pre 为刚访问的结点

0 B ∧ 0 0 C 0

10 ∧ D 0 0 ∧ E ∧ 0 0 ∧ F ∧ 0

01 G ∧ 1
0

pre
5.5.2 线索二叉树
中序线索链表 头指针
中序遍历二叉链表
的建立过程 p 为正在访问的结点
0 A 0 pre 为刚访问的结点
p

0 B ∧ 01 0 C 0

pre

10 ∧ D 0 0 ∧ E ∧ 0 0 ∧ F ∧ 0

01 G 10
5.5.2 线索二叉树
中序线索链表 头指针
中序遍历二叉链表
的建立过程 p 为正在访问的结点
0 A 0 pre 为刚访问的结点
pre

0 B 01 0 C 0

10 ∧ D 0 01 ∧ E ∧ 0 0 ∧ F ∧ 0

p
01 G 10
5.5.2 线索二叉树
中序线索链表 头指针
中序遍历二叉链表
的建立过程 p 为正在访问的结点
0 A 0 pre 为刚访问的结点

0 B 01 0 C 0

p
10 ∧ D 0 01 E ∧ 01 0 ∧ F ∧ 0

pre
01 G 10
5.5.2 线索二叉树
中序线索链表 头指针
中序遍历二叉链表
的建立过程 p 为正在访问的结点
0 A 0 pre 为刚访问的结点

0 B 01 0 C 0

pre
10 ∧ D 0 01 E 01 01 ∧ F ∧ 0

p
01 G 10
5.5.2 线索二叉树
中序线索链表 头指针
中序遍历二叉链表
的建立过程 p 为正在访问的结点
0 A 0 pre 为刚访问的结点

0 B 01 0 C 0

10 ∧ D 0 01 E 01 01 F ∧ 01

pre
01 G 10
以结点 p 为根的子树中序线索化
void InThreading (BiThrTree p)
//pre 是全局变量 , 初始化时其右孩子指针为空
{ if (p)
{ InThreading( p->lchild ); // 左子树线索化
if ( !p->lchild )
{ p->LTag=1; p->lchild=pre; } // 前驱线索
if ( !pre->rchild )
{ pre->RTag=1; pre->rchild=p; } // 后继线索
pre = p; // 保持 pre 指向 p 的前驱
InThreading(p->rchild); // 右子树线索化
}
}
void InorderThreading(BiThrTree & Thrt, BiThrTree T)
{ // 中序遍历二叉树 T, 并将其中序线索化 , Thrt 指向头结点 .
Thrt =new BiThrNode; // 建头结点
Thrt ->LTag = 0;
Thrt ->RTag = 1;
Thrt ->rchild = Thrt ; // 右指针回指
if ( !T ) Thrt ->lchild = Thrt ; // 若二叉树空 , 则左指针回指
else {
Thrt ->lchild = T; pre = Thrt; // 将头结点与树相连
InThreading(T); // 中序遍历进行中序线索化
pre ->rchild = Thrt;
pre ->RTag = 1; // 最后一个结点线索化
Thrt ->rchild = pre;
}
}
遍历中序线索二叉树
 算法思想
从根结点出发沿左指针向下,到达最左下结点 *p,
它是中序遍历的第一个结点,访问 *p
反复查找当前结点 *p 的后继结点,直至遍历结束
 若 p->RTag==1, 则其后继结点的指针为 p->rchild;
 否则,其后继为结点 *p 的右子树的最左下结点;访问
找到这个后继结点。
遍历中序线索二叉树
 算法 5.9(p132)
void InOrderTraverse_Thr(BiThrTree T)
//T 指向头结点,头结点的左链 lchild 指向树的根结点
{
p=T->lchild; // 从头结点进入到根结点;
while( p!=T) // 空树或遍历结束时, p==T
{
while(p->LTag==0) p=p->lchild; // 先找到中序遍历起点
cout<<p->data; // 访问左子树为空的结点
while(p->RTag==1 && p->rchild!=T) //p->rchild=T 即为最后一个结点
{
p=p->rchild; cout<<p->data;
}
p=p->rchild;
}
}
5.6 树和森林
 5.6.1 树的存储结构
双亲表示法
孩子表示法
孩子兄弟表示法
 5.6.2 森林与二叉树的转换
 5.6.3 树和森林的遍历
5.6.1 树的存储结构
1 双亲表示法
实现
 定义结构数组存放树的结点,每个结点含两个域:
 数据域:存放结点本身信息
 双亲域:指示本结点的双亲结点在数组中位置
特点 typedef struct PTNode{
 找双亲容易,找孩子难 TElemType data;
int parent;
}PTNode;
typedef struct{
PTNode nodes[100];
int r,n; // 根的位置和结点数
}PTree ;
5.6.1 树的存储结构
1 双亲表示法 0 A -1
A
1 B 0
2 C 0
B C
3 D 1
4 E 1
D E F G
5 F 2
6 G 2
H I 7 H 3
8 I 3
缺点:求结点的孩子时需要遍历整个结构。
5.6.1 树的存储结构
 孩子表示法
多重链表
 每个结点有多个指针域,分别指向其子树的根
 结点同构
» 结点的指针个数相等,为树的度 D
» 缺点:容易造成空间浪费

data child1 child2 …. childD

 结点不同构
» 结点指针个数不等,为该结点的度 d
» 缺点:操作不方便
data degree child1 child2 … childd
5.6.1 树的存储结构
 2 孩子表示法
孩子链表
 每个结点的孩子结点用单链表存储,再用含 n 个元素
的结构数组指向每个孩子链表
typedef struct CTNode {    
    int child; // 孩子结点的序号
    struct CTNode *next; // 孩子结点的指针域
   } *ChildPtr; // 孩子链表的结点
typedef struct {
    ElemType data; // 结点的数据元素
    ChildPtr firstchild; // 孩子链表头指针
   } CTBox;
typedef struct {
    CTBox nodes[Maxnode];
    int n, r;   // 结点数和根结点的位置
   } CTree; // 树结构
5.6.1 树的存储结构
 2 孩子表示法
孩子链表
5.6.1 树的存储结构
 2 孩子表示法
孩子链表
5.6.1 树的存储结构
3 孩子兄弟表示法
又称二叉树表示法,或二叉链表表示法
即以二叉链表作存储结构,链表中结点的两个链
域分别指向该结点的第一个孩子和下一个兄弟,
分别命名为 firstson 域和 nextsibling 域。
firstchild data nextsibling

指向左孩子 typedef struct CSNode 指向右兄弟


{
ElemType data;
struct CSNode * firstson,* nextsibling;
}CSNode,*CSTree;
5.6.1 树的存储结构
 3 孩子兄弟表示法
5.6.2 森林与二叉树的转换
 树与二叉树的转换
 森林与二叉树的转换
树与二叉树的转换
树 对应 二叉树
A A
A ^

B C E 存储 B
存储 ^ B
C
D C
D E
^ D ^ 解释
解释 A ^
^ E ^
A ^ ^ B

^ B C ^ E ^ C

^ D ^ ^ D ^ ^ E ^
树与二叉树的转换
 将树转换成二叉树
 加线:在兄弟之间加一连线
 抹线:对每个结点,除了其左孩子外 , 去除其与其余孩子之间的关系
 旋转:将同一双亲的孩子连线绕左孩子旋转 45 度角。
A A A

B C D B C D B C D

E F G H I E F G H I E F G H I

A
A B
E C
B C D
F D
E F G H I
G H
树转换成的二叉树其右子树一定为空
I
树与二叉树的转换
 将二叉树转换成树
 加线:若 p 结点是双亲结点的左孩子,则将 p 的右孩子,右孩子的
右孩子,……沿分支找到的所有右孩子,都与 p 的双亲用线连起来
 抹线:抹掉原二叉树中双亲与右孩子之间的连线
 调整:将结点按层次排列,形成树结构
A A A A
B B B B
E C E C E C E C
F D F D F D F D
G H G H G H G H

A I I I I

B C D
操作要点:把右孩子变为兄弟!
E F G H I
5.6.2 森林与二叉树的转换

由此,通过
存储结构的
一 致 性 作
为 " 媒
介 " ,可建
立森林和二
叉树之间一
一对应的关
系。
思考:如果没有告诉你,这是上图所示的森林的
存储结构,你是否会将它看成是一棵二叉树呢?
5.6.2 森林与二叉树的转换
森林和二叉树的对应关系

设森林
F = ( T1, T2, …, Tn )
T1 = ( root , t11, t12, …, t1m )
二叉树
B =( LBT, Node(root), RBT )
5.6.2 森林与二叉树的转换
由森林转换成二叉树的转换规则为 :
若 F = Φ ,则 B = Φ;
否则,
由 ROOT( T1 ) 对应得到
Node(root);
由 (t11, t12, …, t1m ) 对应得到
由LBT;
(T2, T3,…, Tn ) 对应得到 RBT 。
5.6.2 森林与二叉树的转换
root
T1
T2,…,Tn

T11,T12,…,T1m

LBT RBT
5.6.2 森林与二叉树的转换
森林转换为二叉树
即 F={T1, T2, …,Tm} B={root, LBT,
RBT}
T1 = ( root , t11, t12, …, t1m )
方法一:
若 F = Φ ,则 B = Φ; 否则,
① 将 F={T1, T2,⋯,Tn} 中的每棵树转换成二叉树。
② 按给出的森林中树的次序,从最后一棵二叉树
开始,每棵二叉树作为前一棵二叉树的根结点的
右子树,依次类推,则第一棵树的根结点就是转
换后生成的二叉树的根结点。
方法二:森林直接变兄弟,再转为二叉树
5.6.2 森林与二叉树的转换
方法二:森林直接变兄弟,再转为二叉树
A E G
A

B C D F H I
B E

J C F G
A
A E G
D H

B C D F H I I

兄弟相连 长兄为父 J
J
孩子靠左 头根为根
5.6.2 森林与二叉树的转换
 二叉树转换成森林
 抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到的所有
右孩子间连线全部抹掉,使之变成孤立的二叉树
 还原:将孤立的二叉树还原成树
A
A E G
B E
B H
G F
C F C I
H
D D J
I

J A E G

操作要点:把最右边的 B C D F H I
子树变为森林,其余右 J
子树变为兄弟
5.6.2 森林与二叉树的转换
 练习 1
将下面由三棵树组成的森林转换成二叉树
5.6.2 森林与二叉树的转换
 练习 2
将下面的二叉树转换成森林
5.6.3 树和森林的遍历
 树的遍历
先根遍历
 先访问树的根结点,然后依次先根遍历根的每棵子树
后根遍历
 先依次后根遍历每棵子树,然后访问根结点
层次遍历
 先访问第一层上的结点,然后依次遍历第二层,…,
第 n 层上的结点
5.6.3 树和森林的遍历
 树的遍历 A

B C D

E F G H

I J K L M

N O
先根遍历:A B E F I G C D H J K L N O M

后根遍历:E I F G B C J K N O L M H D A

层次遍历:A B C D E F G H I J K L M N O
5.6.3 树和森林的遍历
A

B C D 先根遍历
ABEFCDG
E F G
A 树的先根遍历等价于
B 二叉树的先序遍历!
E C 先序遍历
ABEFCDG
F D

G
5.6.3 树和森林的遍历
A

B C D 后根遍历
EFBCGDA
E F G
A 树的后根遍历等价于
B 二叉树的中序遍历!

E C 中序遍历
EFBCGDA
F D

G
5.6.3 树和森林的遍历
 森林的遍历
先序遍历
 若森林不空,则可依下列次序进行遍历 :
 访问森林中第一棵树的根结点;
 先序遍历第一棵树中的子树森林;
 先序遍历除去第一棵树之后剩余的树构成的森林。
中序遍历
 若森林不空,则
 中序遍历森林中第一棵树的子树森林 ;
 访问森林中第一棵树的根结点 ;
 中序遍历森林中 ( 除第一棵树之外 ) 其余树构成的森林。
5.6.3 树和森林的遍历
 对下图所示森林进行先序和中序遍历

先序遍历: ABCDEFIGJH
中序遍历: BDCAIFJGHE
5.6.3 树和森林的遍历
 练习:用先序和中序两种方法遍历该森林;
写出遍历后的结点序列。

先序遍历森林 :ABCD EFIGJH KLMOPN


中序遍历森林 :BDCA IFJGHE LOPMNK
5.6.3 树和森林的遍历

树、森林的遍历和二叉
树遍历的对应关系 ?
树 森林 二叉树

先根遍历 先序遍历 先序遍历

后根遍历 中序遍历 中序遍历


5.7 哈夫曼树及其应用
 哈夫曼树的相关术语
路 径
 从树中一个结点到另一个结点之间的分支构成这两个
结点间的路径 x

路径长度 y z
 路径上的分支数目
a b c d
带权路径长度
7 5 2 4
 结点到根的路径长度与结点上权的乘积

树的带权路径长度 (weighted path length)


 树中所有叶子结点的带权路径长度之和
5.7 哈夫曼树及其应用
 哈夫曼树
设有 n 个权值 {w1,w2,…,wn} ,构造一棵有 n 个叶
子结点的二叉树,每个叶子的权值为 wi ,则 wpl
最小的二叉树叫哈夫曼树。
例子
 有 4 个结点,权值分别为 7 , 5 , 2 , 4 ,构造有 4
个叶子结点的二叉树
7 a
c 2
4 d a b c d 5 b
a b 7 5 2 4
WPL=7*2+5*2+2*2+4*2=36 2 c d 4
7 5
WPL=7*3+5*3+2*1+4*2=46 WPL=7*1+5*2+2*3+4*3=35
5.7 哈夫曼树及其应用
 哈夫曼树的构造过程
 (1) 初始化:根据给定的 n 个权值 {w1,w2,…,wn} ,构
成 n 棵二叉树的集合 F={T1,T2,…,Tn} ,其中每棵二
叉树 Ti 只有一个带权为 wi 的根结点,其左右子树均
空。
 (2) 选取与合并:在 F 中选取两棵根结点的权值最
小的树作为左右子树,构造一棵新的二叉树,且置新
的二叉树的根结点的权值为其左、右子树上根结点的
权值之和。 
 (3) 删除与加入:在 F 中删除这两棵树,同时将新
得到的二叉树加入 F 中。 
 (4) 重复 (2) 和 (3) ,直到 F 只含一棵树为止。这棵
树便是所求的哈夫曼树。
5.7 赫夫曼树及其应用
W = {2 , 3 , 4 , 5} 哈夫曼树的构造过程

第 1 步:初始化 2 4 5 3

第 2 步:选取与合 5

2 3

第 3 步:删除与加 4 5 5

2 3
5.7 赫夫曼树及其应用
W = {2 , 3 , 4 , 5} 哈夫曼树的构造过程

重复第 2 4 5 5

2 3
9

4 5

重复第 3 9 5

4 5 2 3
5.7 赫夫曼树及其应用
W = {2 , 3 , 4 , 5} 哈夫曼树的构造过程

重复第 2 9 5

4 5 2 3

重复第 3 14

9 5

4 5 2 3
5.7 哈夫曼树及其应用
基本思想:使权大的结点靠近根

7 5 2 4
a b c d
18
7 5 6 7 11
a b a 7
2 4 5 a
c d b 5
2 4 b
c d 2 4
c d
7
a
5 操作要点:对权值的合并、删除与替
b 换,总是合并当前值最小的两个
2 4
c d
练习
w={5, 29, 7, 8, 14, 23, 3, 11}
哈夫曼编码
 在远程通讯中,要将待传字符转换成二进制
的字符串,怎样编码才能使它们组成的报文
在网络中传得最快?
A 00 A 0
B 01 ABACCDA B
C
00
1
C 10
D 11 D 01

000110010101100 000011010

出现次数较多的字符采用尽可能短的编码
哈夫曼编码
A 0

ABACCDA B
C
00
1
D 01
    0000
AAAA ABA BB
重码 000011010

关键:要设计长度不等的编码,则必须使任一字符的
编码都不是另一个字符编码的前缀-前缀编码
哈夫曼编码
ABACCDA
A—0
采用二叉树设 B—110
计前缀编码 C—10
D—111

0 1
0110010101110
A 0 1
C 左分支用“ 0”
0 1
B D 右分支用“ 1”
哈夫曼编码的构造
 基本思想
概率大的字符用短码,小的用长码
 例:某系统在通讯时,只出现 C , A , S ,
T , B 五种字符,其出现频率依次为
2 , 4 , 2 , 3 , 3 ,试设计 Huffman 编
码。 14
T   00 0 1
B   01 6 8 1
0 1 0
A   10 3 3 4 4
0 1
C   T B A 2 2
110 C S
S 
哈夫曼编码的译码过程
 分解接收字符串:遇“ 0” 向左,遇“ 1” 向
右;一旦到达叶子结点,则译出一个字符,
反复由根出发,直到译码完成。
1 0110010101110
0
A 0 1
C 0 1
B D

ABACCDA
特点:每一码都不是另一码的前缀,绝不会错译 !
哈夫曼树构造算法的实现
 一棵有 n 个叶子结点的 Huffman 树有多少个
结点?
2n-1 typedef struct
 采用顺序存储结构 { int weight;
结点类型定义 int
一维结构数组 parent,lchild,rchild;
}HTNode,*HuffmanTree
设置一个数组 huffTree[2n-1] 保存哈夫曼树中各点的信
息,数组元素的结点结构 ; :

weight lchild rchild parent


 构造算法见算法 5.10
哈夫曼树构造算法
 算法思想
1) 初始化 HT[1..2n-1] : lch=rch=parent=-1
2) 根据 w 数组设置 HT[1..n] 的 weight 值
3) 进行以下 n-1 次合并,依次产生 HT[i] , i=n+1..2n-
1:
3.1) 在 HT[1..i-1] 中选两个未被选过的 weight 最小
的两个结点 HT[s1] 和 HT[s2] ( 从 parent = -1 的结点
中选 )
3.2) 修改 HT[s1] 和 HT[s2] 的 parent 值:
parent=i
3.3) 置 HT[i] : weight=HT[s1].weight +
HT[s2].weight ,lch=s1, rch=s2
赫夫曼树构造算法实现 - 举例
weight parent lchild rchild
2 4 5 3
0 2 -1 -1 -1
1 4 -1 -1 -1
2 5 -1 -1 -1
3 3 -1 -1 -1
4 -1 -1 -1
5 -1 -1 -1
6 -1 -1 -1

初 态
赫夫曼树构造算法实现 - 举例
2 4 5 3 weight parent lchild rchild
i1
4 5 5 0 2 4 -1 -1 -1
1 4 -1 -1 -1
2 3
2 5 -1 -1 -1
i2
3 3 4 -1 -1 -1
k
4 5 -1 0 -1 3 -1
5 -1 -1 -1
6 -1 -1 -1

过程
赫夫曼树构造算法实现 - 举例
4 5 5 weight parent lchild rchild

0 2 4 -1 -1 -1
2 3 i1
1 4 5 -1 -1 -1
5 9
2 5 -1 -1 -1
4 5 3 3 4 -1 -1 -1
i2
4 5 5 -1 0 -1 3 -1
2 3 k 5
9 -1 1 -1 4 -1
6 -1 -1 -1

过程
赫夫曼树构造算法实现 - 举例
5 9 weight parent lchild rchild

0 2 4-1 -1 -1
4 5
1 4 5-1 -1 -1
i1
2 3 2 5 6-1 -1 -1
14 3 3 4-1 -1 -1
4 5 5-1 0 -1 3 -1
5 9 i2
5 9 6-1 1 -1 4 -1
4 5 k 6 14 -1 2 -1 5 -1

过程
2 3
哈夫曼编码的几点结论
 哈夫曼编码是前缀编码,即任一字符的编码都不是
另一字符编码的前缀
 哈夫曼编码树中没有度为 1 的结点。若叶子结点的
个数为 n ,则哈夫曼编码树的结点总数为 2n-1
 发送过程
 根据由哈夫曼树得到的编码表送出字符数据
 接收过程
 按左 0 、右 1 的规定,从根结点走到一个叶结点,完成一
个字符的译码。反复此过程,直到接收数据结束
练习
设有正文 AADBAACACCDACACAAD, 字符集为 A,B,C,D,
设计一套二进制编码,使得上述正文的编码最短。
计算它的带权路径长度。
字符 A , B , C , D 出现的次数为
9,1,5,3 。

其哈夫曼编码如下 A:1 , B:000 , C:01 , D:001


wpl=(1+3)*3+5*2+9*1=31
第 5 章 树和二
小结 : 叉树 树 结 构

树 相互转换 二叉树

逻辑结构 存储结构 逻辑结构 存储结构

抽 双 孩 孩 二 特 二 抽 二 顺
树 基 树 二 三 线
象 亲 子 子 叉 殊 叉 象 叉 序
的 本 数 的 表 表 兄 叉 叉 索
树 的 树 数 树 存

定 术 据 遍 示 示 的 二 的 据 的 储 链 链 链

类 法 法 示 定 叉 性 类 遍 结
义 语 历 表 表 表
型 法 义 树 质 型 历 构

⑴ 先根遍历 ⑴ 单支树 ⑴ 先序遍历 ⑴ 遍历操作的


⑵ 后根遍历 ⑵ 满二叉树 ⑵ 中序遍历
⑶ 层序遍历 ⑶ 完全二叉树 ⑶ 后序遍历 实现
⑷ 层序遍历 ⑵ 基于遍历的

其他算法
小结
1 定义和性质
树 顺序结构
2 存储结构
二叉链表
链式结构
三叉链表
森林 二叉树 先序遍历
3 遍历 中序遍历
后序遍历 先序线索树
4 线索化:线索树 中序线索树
赫夫曼树 后序线索树

赫夫曼编码
小结
 掌握二叉树的基本概念、性质和存储结构
 熟练掌握二叉树的前、中、后序遍历方法
 了解线索化二叉树的思想
 熟练掌握:哈夫曼树的实现方法、构造哈夫
曼编码的方法
 熟练掌握:森林与二叉树的转换,森林的遍
历方法
练习
一、选择题
1. 已知一算术表达式的中缀形式为 A+B*C-D/E ,
后缀形式为 ABC*+DE/- ,其前缀形式为 (D )
A . -A+B*C/DE B. -A+B*CD/E
C . -+*ABC/DE D. -+A*BC/DE
2. 设有一表示算术表达式的二叉树,它所表示的
算术表达式是( C ) /

A. A*B+C/(D*E)+(F-G) + +

B.(A*B+C)/(D*E)+(F-G) *
C
* -
D E F G
C.(A*B+C)/(D*E+ ( F-G ) )
A B

D. A*B+C/D*E+F-G
3. 在下述结论中,正确的是( D )
①只有一个结点的二叉树的度为 0;
② 二叉树的度为 2 ;
③二叉树的左右子树可任意交换 ;
④ 深度为 K 的完全二叉树的结点个数小于或等
于深度相同的满二叉树。
A .①②③ B .②③④ C .②④ D.
①④
4. 若一棵二叉树具有 10 个度为 2 的结点, B 5 个度
为 1 的结点,则度为 0 的结点个数是( )
A.9 B . 11 C . 15 B D .不
确定
5. 有关二叉树下列说法正确的是( )
A .二叉树的度为 2
B .一棵二叉树的度可以小于 2
6. 具有 10 个叶结点的二叉树中有(B )个
度为 2 的结点。
A.8 B.9 C . 10
D . ll
7. 一棵完全二叉树上有 1001 E 个结点,其中
叶子结点的个数是( )
A . 250 B . 500 C . 254 D .
505 E .以上答案都不对
由二叉树结点的公式: n=n0+n1+n2=n0+n1+(n0-
1)=2n0+n1-1 , 因为 n=1001, 所以 1002=2n0+n1,
在完全二叉树树中, n1 只能取 0 或 1, 在本题中
只能取 0 ,故 n=501 ,因此选 E 。
8. 二叉树的第 i 层上最多含有结点数为( C )
A . 2i B . 2i-1-1 C . 2i-1 D.
2i -1 C
9. 一个具有 1025 个结点的二叉树的高 h 为(

A . 11 B . 10 C . 11 至 1025 之间
D . 10 至 1024 之间 B
10. 一棵二叉树高度为 h, 所有结点的度或为 0 ,或
为 2 ,则这棵二叉树最少有 ( ) 结点 D
A . 2h B . 2h-1 C . 2h+1 D.
h+1
11. 对于有 n 个结点的二叉树 , 其高度为( )
A
A . nlog2n B . log2n
C . log2n+1 D .不确定
12. 一棵具有 n 个结点的完全二叉树的树高度
13. 深度为 h 的满 m 叉树的第 k 层有( A )个结点。
(1=<k=<h)
A . mk-1 B . mk-1 C . mh-1
D . mh-1
14.已知一棵二叉树的前序遍历结果为 ABCDEF,A
中序遍历结果为 CBAEDF, 则后序遍历的结果为 (
)
A . CBEFDA B . FEDCBA C.
CBEDFA D .不定 B
15.某二叉树中序序列为 A,B,C,D,E,F,G ,后序序
列为 B,D,C,A,F,G,E 则前序序列是:( )
A . E,G,F,A,C,D,B B . E,A,C,B,D,G,F
C . E,A,G,C,F,B,D D .上面的都不对 B
16.在二叉树结点的先序序列,中序序列和后序序
列中,所有叶子结点的先后顺序( )
A .都不相同   B .完全相同 C .先序和
中序相同,而与后序不同 D .中序和后序相同,
17. 一棵非空的二叉树的先序遍历序列与后序遍历
序列正好相反,则该二叉树一定满足( C )
A .所有的结点均无左孩子
B .所有的结点均无右孩子
C .只有一个叶子结点
D .是任意一棵二叉树
18. 引入二叉线索树的目的是( A )
A .加快查找结点的前驱或后继的速度
B .为了能在二叉树中方便的进行插入与删除
C .为了能方便的找到双亲
D .使二叉树的遍历结果唯一
19. n 个结点的线索二叉树上含有的线索数为(C

A . 2n B . n-1 C.n+1 D.
n
20. 树的后根遍历序列等同于该树对应的二叉树的
( B )
A. 先序序列 B. 中序序列 C. 后序序列 D. 顺序
序列
22. 在下列存储形式中,哪一个不是树的存储形式?
D
( )
A .双亲表示法 B .孩子链表表示法
C .孩子兄弟表示法 D .顺序存储表示法
23. 利用二叉链表存储树,则根结点的右指
针是( C )。
A .指向最左孩子 B .指向最
右孩子 C .空
D .非空
24. 设给定权值总数有 n 个,其哈夫曼树的
结点总数为( )
D 。
A .不确定 B . 2n C.
2n+1 D . 2n-1
25. 设 F 是一个森林, B 是由 F 变换得的二叉树。
若 F 中有 n 个非终端结点,则 B 中右指针域为空
的结点有( )个。
A . n-1 B.n C . n+1 D.
n+2

1. 设终端结点个数为 t ,则总的结点个数为 n+t ;


2. 转化成二叉树后,有 n+t+1 个指针域为空;
3. 由于终端结点没有孩子,在转化为二叉树时,
有 t 个结点左指针域为空;
4. 所以,右指针域为空的个数为 (n+t+1)-t=n+1
三、填空题
1. 二叉树由( 根 ) 结 ,点( )子, 树
左 ( )三个基
右子树
本单元组成。
2. 在二叉树中,指针 p 所指结点为叶子结点的条
件是( )
p->lchild==null && p->rchlid==null
3. 深度为 K 的完全二叉树至少有( 2k-1 )个结点,
至多有( 2k-1 )个结点, K 和结点总数 N 之间
的关系是( ) 。 N+1
K=log 2
4. 如某二叉树有 20 个叶子结点,有 30 个结点仅
有一个孩子,则该二叉树的总结点数为() 69
6. 设二叉树中每个结点均用一个字母表示,若一
个结点的左子树或右子树为空,用 .表示,现
前序遍历二叉树,访问的结点的序列为
ABD.G...CE.H..F ..,则中序遍历二叉树时,
访问的结点序列为( .D.G.B.A.E.H.C.F.
)后序遍历二叉树
时,访问的结点序列为( )。
...GD.B...HE..FCA

7. 现有按中序遍历二叉树的结果为 abc ,问有(


)种不同的二叉树可以得到这一遍历结果,这
些二叉树分别是:
5
8. 二叉树的
1 )先序序列与后序序列相同的条件是(
或为空树,或为只有根结点的二叉树

2 )中序序列与后序序列相同的条件是(

或为空树,或为任一结点至多只有左子树的二叉
树3 )先序序列与中序序列相同的条件是(

或为空树,或为任一结点至多只有右子树的二叉
4 )中序序列与层次遍历序列相同的条件是(
树 )。

或为空树,或为任一结点至多只有右子树的二叉

9. 设 F 是由 T1,T2,T3 三棵树组成的森林 , 与 F 对
应的二叉树为 B, 已知 T1,T2,T3 的结点数分别
为 n1,n2 和 n3 则二叉树 B 的左子树中有(
n1-1
)个结点,右子树中有( n2+n3 )个结点。
10.有数据 WG={7,19,2,6,32,3,21,10}
则所建 Huffman 树的树高是( 6 ),
带权路径长度 WPL 是(261 )。

(10+7)*4+(2+3)*5+6*4+(19+21)*2+32*2=261
11.有一份电文中共使用 6 个字符 :a,b,c,d,e,f,
它们的出现频率依次为 2,3,4,7,8,9 ,试构造
一棵哈夫曼树,则其加权路径长度 WPL80 为(
),字符 c 的编码是( ) )
100/000/001/101…( 不唯一
(7+8)*2+4*3+(2+3)*4+9*2=80
2 、设一棵二叉树的先序遍历序列: A B D
F C E G H ,中序遍历序列: B F D A G E
H C ,画出这棵二叉树。

B C

D E

F G H
3 、已知一棵二叉树的中序: GLDHBEIACJFK
后序: LGHDIEBJKFCA ,画出这棵二叉树:
4 、一棵非空的二叉树其先序序列和后序序列
正好相反,画出这棵二叉树的形状。
分析:先序序列是“根左右” 后序序列是
“左右根”,可见对任意结点,若至多只有
左子女或至多只有右子女,均可使前序序列
与后序序列相反,图示如下:
5 、一棵二叉树的先序、中序、后序序列如下,
其中一部分未标出,请将其标出并构造出该
二叉树。
B _ _ C DF E _ G HJ I _ K
先序序列A :
中序序列 : CE BD _ _ HF A _ J K I G
后序序列C : _ E F KD B _ J IG H _ A
7 、设有正文 AADBAACACCDACACAAD, 字符集为
A,B,C,D, 设计一套二进制编码,使得上述正
文的编码最短。计算它的带权路径长度。

字符 A , B , C , D 出现的次数为
9,1,5,3 。

其 哈 夫 曼 编 码 如 下
A:1 , B:000 , C:01 , D:001
wpl=(1+3)*3+5*2+9*1=31

You might also like