Transcript Document

第六章
树和二叉树
主要内容







树的定义与基本术语
二叉树的特性(重点)
二叉树的存储结构(重点)
二叉树的遍历(重点)
线索二叉树
树和森林与二叉树的转换(重点)
最优树和哈夫曼编码(重点)
教学要求

掌握树的基本定义及其相关的术语的含义;

熟练掌握二叉树的结构特性,了解相应的证明
方法 ;

熟悉三种遍历二叉树的递归算法;

理解二叉树线索化的实质及线索化的过程;

掌握树、森林与二叉树的转换及其各自遍历的
对应关系;

掌握哈夫曼编码的原理及实现方法。
教学重点及难点
教学重点:
二叉树的定义和逻辑特点,二叉树的链式存储
结构的组织方式,二叉树的三种遍历方法及其算
法,一般树转化为二叉树的方法,哈夫曼树及哈
夫曼编码,哈夫曼编码的应用。
教学难点:
二叉树的递归定义,二叉树链式存储结构的组
织方式,三种遍历的区别,哈夫曼编码。
树的定义与基本术语
1、树的定义
数据对象 D:D是具有相同特性的数据元素的集合。
数据关系 R: 若D为空集,则称为空树;
否则:
(1) 在D中存在唯一的称为根的数据元素root,
(2) 当n>1时,其余结点可分为m (m>0)个互
不相交的有限集T1, T2, …, Tm, 其中每一
棵子集本身又是一棵符合本定义的树,
称为根root的子树。
B
基本操作 P:
E
A
C D
F G
2、树的表示方法:
图示法、集合(文氏)法、凹入法、广义表法
A
A
D
C
B
E
F G
D
B
E
F
C
G
A
B
C
D
E
F
G
例如:
A
C
B
E
F
K
G
D
H
I
J
L
M
A( B(E, F(K, L)), C(G), D(H, I, J(M)) )
树根
T1
T2
T3
3、基本术语
结点:数据元素+若干指向子树的分支
结点的度:分支的个数
树的度:树中所有结点的度的最大值
叶子结点:度为零的结点
A
分支结点:度大于零的结点
B
D
E
F
C
G
(从根到结点的)路径:
由从根到该结点
所经分支和结点构成 E
A
B
C
F
G
D
H
I
J
M
孩子结点、双亲结点、 K L
兄弟结点、堂兄弟
祖先结点、子孙结点
结点的层次: 假设根结点的层次为1,第l 层
的结点的子树根结点的层次
为l+1
树的深度: 树中叶子结点所在的最大层次
有向树:
(1) 有确定的根;
(2) 树根和子树根之间为有向关系。
有序树:
子树之间存在确定的次序关系。
无序树:
子树之间不存在确定的次序关系。
森林:
是 m(m≥0)棵互
不相交的树的集合
root
F
A
B
E
C
F
K
G
L
任何一棵非空树是一个二元组
Tree = (root,F)
其中:root 被称为根结点,
F 被称为子树森林
D
H
I
J
M
线性结构
第一个数据元素
(无前驱)
树型结构
根结点
(无前驱)
最后一个数据元素
(无后继)
多个叶子结点
(无后继)
其它数据元素
(一个前驱、
一个后继)
其它数据元素
(一个前驱、
多个后继)
二叉树
1、定义
2、性质
3、存储结构
4、基本操作
5、线索化过程
1、定义:二叉树或为空树;或是
由一个根结点加上两棵分别称为左子树
和右子树的、互不交的二叉树组成。
根结点
右子树
A
B
E
C
F
G
D
左子树
H
K
二叉树的五种基本形态:
空树
只含根结点
左右子
树均不
为空树
N
右子树为空树
N
L
左子树为空树
N
N
R
L
R
2、二叉树特性
性质 1 :
在二叉树的第 i 层上至多有2 i-1 个结点。(i≥1)
用归纳法证明:
归纳基: i = 1 层时,只有一个根结点,
2 i-1 = 20= 1;
归纳假设:假设对所有的 j,1≤ j  i,命题成立;
归纳证明:二叉树上每个结点至多有两棵子树,
则第 i 层的结点数 = 2 i-2 2 = 2 i-1 。
性质 2 :
深度为 k 的二叉树上至多含 2k-1
个结点(k≥1)
证明:
基于上一条性质,深度为 k 的二叉树上的结
点数至多为
20+21+       +2k-1 = 2k-1
性质 3 :
对任何一棵二叉树,若它含有n0 个叶
子结点、n2 个度为 2 的结点,则必存在
关系式:n0 = n2+1
证明:
设 二叉树上结点总数 n = n0 + n1 + n2
又 二叉树上分支总数 b = n1 + 2n2
而 b = n-1 = n0 + n1 + n2 - 1
由此, n0 = n2 + 1
两类特殊的二叉树:
满二叉树:指的是
2
1
3
5
6
7
深度为k且含有2k-1个 4
结点的二叉树。
8 9 10 11 12 13 14 15
完全二叉树:树
a
中所含的 n 个结点
b
c
和满二叉树中编号
d
e
f
g
为 1 至 n 的结点一
一对应。
h i j
性质 4 :
具有 n 个结点的完全二叉树的深度
为  log2n +1
证明:
设 完全二叉树的深度为 k
则根据第二条性质得 2k-1≤ n < 2k
即 k-1 ≤ log2 n < k
因为 k 只能是整数,因此, k =log2n + 1
性质 5 :
若对含 n 个结点的完全二叉树从上到下且从左至
右进行 1 至 n 的编号,则对完全二叉树中任意一
个编号为 i 的结点:
(1) 若 i=1,则该结点是二叉树的根,无双亲, 否
则,编号为 i/2 的结点为其双亲结点;
(2) 若 2i>n,则该结点无左孩子,
否则,编号为 2i 的结点为其左孩子结点;
(3) 若 2i+1>n,则该结点无右孩子结点,
否则,编号为2i+1 的结点为其右孩子结点。
概念应用示例
1. 由3个结点所构成的二叉树有 5 种形态。
2. 一棵深度为6的满二叉树有 n1+n2=0+ n2=
n0-1=31 个分支结点和 26-1 =32 个叶子。
注:满二叉树没有度为1的结点,所以分支结点数
就是度为2的结点数。
3. 一棵具有257个结点的完全二叉树,它的深
度为 9 。
注:用 log2(n) +1
4、设一棵完全二叉树有700个结点,则共有 350
个叶子结点。
答:最快方法:用叶子数=[n/2]=350
概念应用示例
5. 设一棵完全二叉树具有1000个结点,则此完全
二叉树有 500 个叶子结点,有 499 个度为2
的结点,有 1 个结点只有非空左子树,有 0
个结点只有非空右子树。
答:最快方法:用叶子数=[n/2]=500 ,
n2=n0-1=499。 另外,最后一结点为2i属于左
叶子,右叶子是空的,所以有1个非空左子树。完
全二叉树的特点决定不可能有左空右不空的情况,
所以非空右子树数=0.
6. 一棵含有n(n>1)个结点的k叉树,可能达到的最
大深度为 n ,最小深度为 。
答: “完全k叉树”
作业1
1.一棵深度为6的满二叉树有______个非终端结
点。
2.若一棵二叉树中有8个度为2的结点,则它有
_____个叶子。
3. 在一棵高度为h的完全二叉树中,所含结点个数
不小于
。
4.树的度为K,有n1个度为1 的结点,n2个度为2的
结点,…..,nk个度为K的结点,问该树有多少
叶子结点?多少非终端结点?
3、二叉树的存储结构
一、 二叉树的顺序存储表示
二、二叉树的链式存储表示
二叉树的顺序存储表示
用一组连续的存储单元存储二叉树的
数据元素。因此,必须把二叉树的所有结
点安排成为一个恰当的序列,结点在这个
序列中的相互位置能反映出结点之间的逻
辑关系。
用编号的方法:与满二叉树的编号对
应,从树根起,自上层至下层,每层自左
至右的给所有结点编号。
完全二叉树的顺序存储表示
a
b
c
d
h
1
2
e
i
3
4
j
5
f
k
6
7
g
l
8
9
A B C D E F G H I
10 11 12
J
K L
一般二叉树的顺序存储表示
a
b
c
d
e
f
g
1 2 3 4 5 6 7 8 9 10 11
a b c d e 0 0 0 0 f g
由于一般二叉树必须仿照完全二叉树那样存储,
可能会浪费很多存储空间,单支树就是一个极
端情况,一个深度为k且只有K个结点的单支树
却需要长度为2K-1的一维数组。
二叉树顺序存储结构特点


结点间关系蕴含在其存储位置中,存取方
便
适于存满二叉树和完全二叉树;做一般二
叉树存储结构,浪费空间。
二叉树的顺序存储表示
#define MAX_TREE_SIZE 100
//二叉树的最大结点数
typedef
TElemType
SqBiTree[MAX_TREE_SIZE];
//0号单元存储根结点
SqBiTree bt;
二叉树的链式存储表示



二叉链表(重点)
三叉链表
线索链表
lchild
二叉链表
typedef struct BiTnode
{ TElementtype data;
空指针个数:2*n0+1*n1+0*n2
struct BiTnode *lchild, *rchild;
} BiTnode,*BiTree;
=2n0+n1
A
=n0+n1+n0
rchild
A ^
B
B
C
data
=n0+n1+n2+1
D
F
E
=n+1
G
^
C
^
^
D
^
E
F
^
在n个结点的二叉链表中,有n+1个空指针域
^
G ^
三叉链表
lchild
data
parent
rchild
typedef struct BiTnode
{ TElementtype data;
struct BiTnode *lchild, *rchild, *parent;
} BiTnode,*BiTree;
A
A
^
^
B
B
C
^ C
D
F
E
D
^
^
E
^
F
G
^ G
^
^
4、基本操作
二叉树的基本操作:
创建二叉树:
遍历二叉树: (重点之重)
 熟练写出二叉树的先序、中序、后序遍历及层次
遍历序列;
 能根据给出组合的遍历序列画出一个相应的唯一的
二叉树;
 会写出三种遍历二叉树及创建二叉树的递归算法;
 能写出层次遍历二叉树的算法。
二叉树的遍历方式
二叉树的遍历方式分为两大类:
 按根、左子树和右子树三个部分进行访问;
 按层次访问。
下面我们将分别进行讨论。
按根、左子树和右子树三部分进行遍历
假如以L、D、R分别表示遍历左子树、遍历根结点
和遍历右子树,遍历整个二叉树则有DLR、
LDR、LRD、DRL、RDL、RLD六种遍历方案。
若规定先左后右,则只有前三种情况,分别规定
为:
DLR——先(根)序遍历,
LDR——中(根)序遍历,
LRD——后(根)序遍历。
先序遍历、中序遍历、后序遍历
A
先序遍历序列:A B D C
C
B
D
中序遍历序列: B D A C
后序遍历序列: D B C A
课堂练习
写出下列二叉树的三种遍历序列:
先序遍历序列:
A
中序遍历序列:
B
C
后序遍历序列:
D
E
G
F
H
课堂练习
写出下列二叉树的三种遍历序列:
A
B
D
C
F
E
G
课堂练习
写出下列二叉树的三种遍历序列。
(该二叉树表示表达式a+b*(c-d)-e/f )
-
/
+
a
e
*
-
b
c
d
f
一个表达式可以表示为一棵二叉树
对该二叉树分别进行前序、中序和后序遍历,
可以得到表达式的前缀、中缀和后缀表示。
例如:一个中缀表示的表达式 a+b*(c - d)-e/f
它的后缀表达式为:
a bcd- * + ef/ -
结论
(1)遍历操作实际上是将非线性结构线
性化的过程,其结果为线性序列,并根
据采用的遍历顺序分别称为先序序列、
中序序列或后序序列;
(2)遍历操作是一个递归的过程,因此,
这三种遍历操作的算法可以用递归函数
实现。
三种遍历的递归算法
1、先序遍历二叉树的递归算法:
void PreOrder(BiTree T)
{ if(T) {
printf(T->data);
/*访问结点的数据域*/
PreOrder(T->lchild);
/*先序递归遍历bt的左子树*/
PreOrder(T->rchild);
/*先序递归遍历bt的右子树*/
}
}
三种遍历的递归算法
2、中序遍历二叉树的递归算法:
void InOrder(BiTree T)
{ if(T) {
InOrder(T->lchild);
/*中序递归遍历bt的左子树*/
printf(T->data);
/*访问结点的数据域*/
InOrder(T->rchild);
/*中序递归遍历bt的右子树*/
}
}
三种遍历的递归算法
3、后序遍历二叉树的递归算法:
void LastOrder(BiTree T)
{ if(T) {
LastOrder(T->lchild);
/*后序递归遍历bt的左子树*/
LastOrder(T->rchild);
/*后序递归遍历bt的右子树*/
printf(T->data);
/*访问结点的数据域*/
}
}
二叉树各种遍历算法分析
先序、中序、后序遍历的递归算法
时间复杂度:O(n)
空间复杂度:O(n)

按层次遍历二叉树
A
B
D
C
E
G
F
H
层次遍历的序列:A B C D E F G H
思想:使用一个队列结构完成这项操作,先将根结点入队;
判断队列是否为空,不空时重复下列操作:
从队列退出一个结点,访问该结点 ;
若其有左孩子,则将其左孩子入队;
若其有右孩子,则将其右孩子入队;
二叉树遍历算法应用
例题1:设计算法按中序次序输出二叉树
T中度为2的结点的值。
void inorder(Bnode *T)
{
if (T)
{ inorder(T->lchild);
if (T->lchild&& T->rchild)
printf(T->data); //当前结点满足条件时输出其值
inorder(T->rchild);
}
}
二叉树遍历算法应用
例题2:设计算法求二叉树T的结点数。
方法1:将某一遍历算法中的访问结点的操作改为计数
操作,即将该结点的数目1累加到一个全局变量n中
int n=0;
void preorderCount(Bitree *T)
{ if (T)
{ n++; //对当前结点计数
preorderCount (T->lchild);
preorderCount (T->rchild);
}
}
二叉树遍历算法应用
例题2:设计算法求二叉树T的结点数。
方法2:按照二叉树的定义来计算结点数
int BitNodeCount (Bitree *T)
{
if (!T) return 0;
else
return 1+ BitNodeCount (T->lchild)+
BitNodeCount (T->rchild)
}
二叉树遍历算法应用
例题3:设计算法求解给定二叉树的高度。
int high(Bitree *T)
{
if (T==NULL) return 0;
else
return max(high(T->lchild),
high(T->rchild))+1;
}
二叉树遍历算法应用
例题4:查找结点
分析:可采用先序遍历递归算法查找值为x的结点。找到后返
回其指针,否则返回NULL。
BiTNode * FindNode(BiTNode *b,ElemType x)
{ BiTNode *p;
if (b==NULL) return NULL;
else if (b->data==x) return b;
else
{
p=FindNode(b->lchild,x);
if (p!=NULL) return p;
else return FindNode(b->rchild,x);
}
}
创建二叉树
问题:
1、如何创建一个二叉树的二叉链表存贮结构?
2、仅给定一个先序遍历序列或一个中序遍历或后序
遍历序列,是否能构造出一棵唯一的二叉树?
A
A
C
B
D
C
B
D
一个先序遍历序列或一个中序遍历或后序遍
历序列,是否能构造出一棵唯一的二叉树?
中序ABC:
先序ABC:
A
A
B
B
A
B
C
C
A
C
C
B
B
C
C
后序ABC:
A
B
A
创建二叉树
用递归的方法创建根结点及其左右子树.为此需要
提供先序完全遍历序列.左右子树为空时用#代替,
表示左子树或右子树为空,即左子树或右子树创建
结束.
例如,先根序列: ABC##DE#G##F###
A
B
C
D
E
F
G
按先序次序构造二叉树
int CreateBiTree(BiTree &T)
{
scanf(“%c”,&ch);
if(ch==‘#’) T=NULL;
else
{ T=(BiTNode *)malloc(sizeof(BiTNode));
if(!T) return 0;
T->data=ch;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
return 1;
}
以括号表示法的字符串创建二叉树
用ch扫描采用括号表示法表示二叉树的字符
串。分以下几种情况:
① 若ch='(':则将前面刚创建的结点
作为双亲结点进栈,并置k=1,表示其后创
建的结点将作为这个结点的左孩子结点;
② 若ch=')':表示栈中结点的左右孩
子结点处理完毕,退栈;
③ 若ch=',':表示其后创建的结点为右
孩子结点;
④ 其他情况,表示要创建一个结点,并根据k
值建立它与栈中结点之间的联系,当k=1时,
表示这个结点作为栈中结点的左孩子结点,
当k=2时,表示这个结点作为栈中结点的右
孩子结点。如此循环直到str处理完毕。算
法中使用一个栈St保存双亲结点,top为其
栈指针,k指定其后处理的结点是双亲结点
(保存在栈中)的左孩子结点(k=1)还是右
孩子结点(k=2)。
void CreateBiTree(BiTNode * &b,char *str)
{
SqStack s;
InitStack(s);
BiTNode *q,*p=NULL;
int k,j=0; char ch;
b=NULL;
/*建立的二叉树初始时为空*/
ch=str[j];
while (ch!='\0')
/*str未扫描完时循环*/
{
switch(ch)
{
case '(':push(s,p);k=1; break;
/*为左孩子结点*/
case ')':pop(s,q);break;
case ',':k=2; break;
/*为孩子结点右结点*/
}
default:p=(BiTNode *)malloc(sizeof(BiTNode));
p->data=ch;p->lchild=p->rchild=NULL;
if (b==NULL) /*p为二叉树的根结点*/
b=p;
else /*已建立二叉树根结点*/
{ switch(k)
{
case 1:GetTop(s,q);q->lchild=p;break;
case 2:GetTop(s,q);q->rchild=p;break;
}
}
}
j++;ch=str[j];
}
重建二叉树
遍历序列的特性:
由二叉树的前序序列和中序序列可以唯一地确定一
棵二叉树,由二叉树的后序序列和中序序列可以唯一
地确定一棵二叉树,由二叉树的前序序列和后序序列
不可唯一地确定一棵二叉树。例如:
先序序列“abcdefg”和中序序列“cbdaegf”
根 左子树 右子树
左子树
根 右子树
abcdefg
a
cbdaegf
bcd b cbd efg e egf
c
d
fg
g
f gf
课堂练习
给出某二叉树的先序序列“ABDEFC”和中
序序列“BEDFAC”,请画出对应的二叉树。
重建二叉树
又如:后序序列“CEFDBA”和中序序列“CBEDFA”,
可以构造一个唯一的二叉树。
左子树 右子树 根
左子树
根 右子树
CEFDBA
A
CEFDB B CBEDF
C EFD D EDF
E
F
CBEDFA
课堂练习
给出某二叉树的中序序列“CBDAEGF”和后
序序列“CDBGFEA”,请画出对应的二叉
树。
作业2
1、写出右边二叉树的四种遍历序列。
D
(先序、中序、后序和层次遍历)
A
B
C
E
G
F
H
2、给出某二叉树的先序序列“ABCDEGF”和中
序序列“CBEGDFA”,请画出对应的二叉树。
3、已知某二叉树的后序遍历序列是dabec,中
序遍历序列是debac,它的先序遍历序列是—
———。
A. acbed B. decab C. deabc D. cedba
4、一棵具有n个结点的完全二叉树以顺序方
式存储在数组A中。设计一个算法构造该
二叉树的二叉链表存储结构。
5、设计一个算法,将一棵以二叉链表存储
的二叉树t按顺序方式存储到数组A中。
6、假设二叉树以二叉链表存储,设计一个
算法,判断一棵二叉树是否为完全二叉树。
5、二叉树遍历的非递归算法及线索化过程

A
二叉树遍历的非递归算法
先序遍历的非递归算法思想:
利用一个栈来存储二叉链表
中结点的地址。从二叉树根结点
开始,沿左子树一直走到末端
(左孩子为空)为止,在走的过程
中,访问所遇结点,并依次把所
遇结点地址进栈,当左子树为空
时,从栈顶退出某结点,并将指
针指向该结点的右孩子。如此重
复,直到栈为空且指针为空止。
C
B
D
A
C
B
D
二叉树先序遍历的非递归算法1
void PreOrderTraver1 (BiTree bt)
{//采用二叉链表存储结构,先序遍历二叉树的非递归算法
InitStack(s); p=bt;
while(p||!StackEmpty(s)) //若(子)树或栈非空
{ if(p)
{ printf(p->data); //访问(子)树根结点;
Push(s, p); p=p->lchild; //把左子树入栈,直到最左下角;
}
else
{ Pop(s,p); //(子树)根指针出栈;
p=p->rchild; //把右子树入栈(此时采用同一种方法,遍历右子树)
}
}
}
二叉树先序遍历的非递归算法2
void PreOrderTraver2 (BiTree bt)
{//采用二叉链表存储结构,先序遍历二叉树的非递归算法
p=bt;
if(p) //若树非空
{
InitStack(s); Push(s, p);
while(!StackEmpty(s)) //若栈非空
{
Pop(s,p); printf(p->data); //访问(子)树根结点;
if (p->rchild) Push(s, p->rchild); //把右子树入栈
if (p->lchild) Push(s, p->lchild); //把左子树入栈
}
}
}
二叉树中序遍历的非递归算法思想
利用一个栈来存储二叉链表中结点的
地址。从二叉树根结点开始,沿左子树
一直走到末端(左孩子为空)为止,在走
的过程中,把依次遇到的结点进栈,待左
子树为空时,从栈中退出结点并访问,然后
再转向它的右子树。如此重复,直到栈
空且指针为空止。
二叉树后序遍历的非递归算法思想
在后序遍历中,当搜索指针指向某一个结点时,
不能马上进行访问,而先要遍历左子树,所以此结
点应先进栈保存,当遍历完它的左子树后,再次回
到该结点,还不能访问它,还需先遍历其右子树,
所以该 结点还必须再次进栈,只有等它的右子树
遍历完后,再次退栈时,才能访问该结点。为了区
分同一结点的两次进栈,引入一个栈次数的标志,
一个元素第一次进栈标志为0,第二次进标志为1,
并将标志存入另一个栈中,当从标志栈中退出的元
素为1时,访问结点。
后序遍历二叉树的非递归算法
void PostOrderTraver(BiTree bt)
{
InitStack(s1); InitStack(s2);
//s1栈存放树中结点地址,s2栈存放进栈标志
p=bt;
do
{
while(p!=NULL)//扫描左结点
{
Push(s1,p);//左结点地址入栈
Push(s2,0); //第一次进栈标志为0
p=p->lchild;
}
后序遍历二叉树的非递归算法
if(!StackEmpty(s1))
{ P=GetTop(s1);f=GetTop(s2) ;
if(f)
{ Pop(s1,p);Pop(s2,f);Printf(p>data);p=NULL;}
else
{Pop(s2,f);Push(s2,1);p=p->rchild;}
}
}while(p||!StackEmpty(s1))
}
线索二叉树
A
A
C
B
C
B
D
D
中序遍历序列:B D A C
A
NULL
lchild
LTag
data
0
NULL
RTag
1
0 A 0
C
B
1 B 0
1 C 1
D
1 D 1
rchild
线索二叉树


构造:

中序:增加pre指针,指向刚访问过的结点

后序:须增加Parent指针域
优点:遍历不需要堆栈。若经常需要遍历二叉
树或查找结点在遍历序列中的前驱和后继,则应
采用线索链表作为存储结构。
建立中序线索二叉树
下面是建立中序线索二叉树的算法。
InOrderThreading (T)算法是将以二叉
链存储的二叉树T进行中序线索化,并返回
线索化后头结点的指针root。
InThreading (p)算法用于对于以*p为根
结点的二叉树中序线索化。在整个算法中
p总是指向当前被线索化的结点,而pre作
为全局变量,指向刚刚访问过的结点,*pre
是*p的前驱结点,*p是*pre的后继结点。
InOrderThreading (T)算法思路是:先创建
头结点root,其lchild域为链指针,rchild域为
线索。将lchild指针指向T,如果T二叉树为空,
则将其lchild指向自身。否则将root的
lchild指向T结点,p指向T结点,pre指向root
结点。再调用InThreading (p)对整个二叉
树线索化。最后加入指向头结点的线索,并将
头结点的rchild指针域线索化为指向最后一
个结点(由于线索化直到p等于NULL为止,所
以最后一个结点为pre)。
Status InOrderThreading (BiThrTree &root,BiThrTree T)
/*中序线索化二叉树*/
{ root=(BiThrTree)malloc(sizeof(BiThrNode)); /*创建头结点*/
if(!root) exit(OVERFLOW);
root->ltag=0;root->rtag=1; root->rchild=root;
if (!T) root->lchild=root;
/*空二叉树*/
else
{ root->lchild=T;
pre=root;
/*pre是*p的前驱结点,供加线索用*/
InThreading(T);
/*中序遍历线索化二叉树*/
pre->rchild=root; /*最后处理,加入指向头结点的线索*/
pre->rtag=1;
root->rchild=pre; /*头结点右线索化*/
}
return OK;
InThreading(p)算法思路是:类似于中序遍
历的递归算法,在p指针不为NULL时,先对p结点
的左子树线索化;若p结点没有左孩子结点,则
将其lchild指针线索化为指向其前驱结点pre,否
则表示lchild指向其左孩子结点,将其ltag置为0;
若pre结点的rchild指针为NULL,将其rchild指
针线索化为指向其后继结点p,否则rchild表示指
向其右孩子结点,将其rtag置为0,再将pre指向p;
最后对p结点的右子树线索化。
BiThrTree pre;
/*全局变量*/
void InThreading(BiThrTree &p)
{
/*对二叉树b进行中序线索化*/
if (p)
{
InThreading(p->lchild);
if (p->lchild==NULL)
/*左子树线索化*/
/*前驱线索*/
{ p->lchild=pre; p->ltag=1;} /*建立当前结点的前驱线索*/
if (pre->rchild==NULL)
/*后继线索*/
{ pre->rchild=p;pre->rtag=1;}/*建立前驱结点的后继线索*/
pre=p;
InThreading(p->rchild); /*递归调用右子树线索化*/
}
}
中序遍历(递归)
遍历线索化二叉树

遍历某种次序的线索二叉树,就是从该次序
下的开始结点出发,反复找到该结点在该次
序下的后继结点,直到终端结点。
void InOrderThr (BiThrTree tb)
/*中序遍历中序线索二叉树的非递归算法*/
{
BiThrTree p=tb->lchild;
/*p指向根结点*/
while (p!=tb)
{
while (p->ltag==0) p=p->lchild; /*找开始结点*/
printf(p->data);
/*访问开始结点*/
while (p->rtag==1 && p->rchild!=tb)
{ p=p->rchild; printf(p->data);/*访问后继结点*/ }
p=p->rchild;
}
}
问题




如何在中序线索二叉树上进行先序遍历?
如何在中序线索二叉树上进行后序遍历?
如何建立先序线索二叉树呢?
如何建立后序线索二叉树呢?(注意)
A
后序线索二叉树
C
B
D
E
F
G
H
在后序线索树中找结点后继较复杂,分3种情况:
(1)若结点x是二叉树的根,则其后继为空;如A
(2)若结点x是其双亲的右孩子或是其双亲的左孩子且其双亲
没有右子树,则其后继即为双亲结点;如E或F
(3)若结点x是其双亲的左孩子,且其双亲有右子树,则其后
继为双亲的右子树上按后序遍历列出的第一个结点。如B
可见,在后序线索化树上找后继时需知道结点双亲,即需带标
志域的三叉链表作存储结构。
二叉树操作汇总
1、创建二叉树
2、输出二叉树(按括号表示法输出)
3、输出二叉树所有叶子结点
4、求结点值为x的结点的层数
5、判断两棵二叉树是否同构
6、输出值为x的结点的所有祖先
7、输出每个叶子结点到根结点的路径
8、查找值为x的结点
二叉树操作汇总
9、交换二叉树的左右子树
10、判断一棵二叉树是否对称同构
11、将完全二叉树的顺序存储表示转换为
二叉链表表示
12、求二叉树的宽度
13、求二叉树的直径
14、求根结点到任意一个结点的路径
15、判断二叉树是否为完全二叉树
等等
作业3

设二叉树bt的存储结构如下:
1
lchild 0
data j
rchild 0
2
0
h
0
3
2
f
0
4
3
d
9
5
7
b
4
6
5
a
0
7
8
c
0
8
0
e
0
9 10
10 1
g
i
0
0
其中,bt为树根结点指针,lchild、rchild
分别为结点的左、右孩子指针域,在这里使用结
点编号作为指针域值,0表示指针域值为空;
data为结点的数据域。
请完成下列各题:
1)画出二叉树bt的树形表示;
2)写出按先序、中序和后序遍历二叉树bt
所得到的结点序列;
3)画出二叉树bt的前序、中序和后序线索
化树。
树和森林
树的性质
 树的表示及其遍历操作
 树、森林与二叉树的对应关系

树的性质




性质1:树中的结点数等于所有结点的度数加1。
性质2:度为m的树中第i层上至多有mi-1个结点
(i≥1) 。
性质3:高度为h的m次树至多有(mh-1)/(m-1)
个结点。
性质4:具有n个结点的m次树的最小高度为
logm (n(m-1)+1)
自测题

n个叶子结点的三次树的最小高度是多少?
(完全三叉树)
h=
log3n+2
或「log3n +1
自测题


树的度为K,有n1个度为1 的结点,n2个
度为2的结点,…..,nk个度为K的结点,
问该树有多少叶子结点?多少非终端结
点?
解:
叶子结点个数为:
n2+2n3+………+(k-1)nk+1
非终端结点有:n1+n2+……….+nk
自测题


若一棵三叉树中度为3的结点为2个,
度为2的结点为1个。度为1的结点为2
个,则该树中总的结点个数和度为0的
结点个数分别是多少?
解:
叶子结点个数为:1+2*2+1=6
总结点有:6+2+1+2=11
a
树的存储结构

b
d e
g h
双亲表示法:主要描述的是结
点的孩子关系。
实现:定义一静态链表存放树的结点,0
1
每个结点含两个域:
数据域:存放结点本身信息
2
双亲域:指示本结点的双亲结点在数 3
组中位置
4
特点:找双亲容易,找孩子难
思考:如何找孩子结点?
c
5
6
7
8
data
parent
a
b
c
d
e
f
g
h
i
-1
0
0
1
1
2
4
4
4
f
i
树的存储结构

孩子表示法:主要描述的是结点的孩子关系。
实现:多重链表:每个结点有多个指针域,分别指
向其子树的根。
结点同构:结点的指针个数相等,为树的度d。
结点不同构:结点指针个数不等,为该结点的度d。
特点:找孩子容易,找双亲难
data
data
child1 child2 ……
degree
child1
childd
child2 ……
childd
孩子表示法
data fc
a
b
d
c
f
e
g
h
i
0
1
2
3
4
5
6
7
8
a
b
c
d
e
f
g
h
i
1
3
5
2
4
^
^
^
^
6
^
^
^
^
思考:如何找双亲结点?
7
8
^
树的存储结构

双亲孩子表示法:双亲表示法和孩子表示法的结合。
data parent fc
0
1
b
c
2
f 3
d
e
4
g h i 5
6
7
8
a
a
b
c
d
e
f
g
h
i
-1
0
0
1
1
2
4
4
4
1
3
5 ^
2 ^
4 ^
^
6
^
^
^
^
7
8^
树的存储结构

孩子兄弟表示法(二叉树表示法) :它通过描述每
个结点的一个孩子和兄弟信息来反映结点之间的层次关
系。
实现:用二叉链表作树的存储结构,链表中每个结
点的两个指针域分别指向其第一个孩子结点和下
一个兄弟结点
特点:操作容易,破坏了树的层次
孩子兄弟表示法(二叉树表示法)
a
b
d
a
c
b
f
e
g
h
^
i
^
d
e
^
g
^
^
^
c
^
f
^
h
^
i
^
森林与二叉树的转换

将一棵树转换成二叉树的方法:将一棵树转换成
二叉树实际上就是将这棵树用孩子兄弟表示法存
储即可,此时,树中的每个结点最多有两个指针:
一个指针指向第一个孩子,另一个指针指向右侧
第一个兄弟。
特点:一棵树转换成二叉树后,根结点没有右孩
子。
a
b c d
e
a
b
c
e
d
森林与二叉树的转换


将森林转换成二叉树的方法:与一棵树转换成二叉
树的方法类似,只是把森林中所有树的根结点看作
兄弟关系,并对其中的每棵树依依地进行转换。
二叉树转换成树或森林:这个过程实际上是树、森
林转换成二叉树的逆过程,即将该二叉树看作是树
或森林的孩子兄弟表示法。比如,若二叉树为空,
树也为空;否则,由二叉树的根结点开始,沿右指
针向下走,直到为空,途经的结点个数是相应森林
所含树的棵数;
课堂练习

将下面树和森林分别转换为二叉树。
a
b
d
B
c
f
e
E
F
K
g
h
i
C
G
L
D
H
I
J
M
树和森林的遍历

树的遍历方法:先根次序遍历、后根次序遍历
和层次遍历

森林的遍历方法:先序遍历、中序遍历和
层次遍历
B
a
b
c
e
d
E
C
F
K
G
L
课堂练习
写出下面树和森林的各种遍历序列。

a
b
d
B
c
f
e
E
F
K
g
h
i
C
G
L
D
H
I
J
M
树和森林的遍历
结论:当树和森林以二叉链表存储时有:
 森林的先序和中序遍历即为其对应的二叉
树的先序和中序遍历
 树的先根遍历和后根遍历即为对应二叉树
的先序遍历和中序遍历
赫夫曼树及应用
赫夫曼(Huffman)树,又称最优树,是一类带权路
径长度最短的树。
内容:
 最优二叉树
 赫夫曼编码
要求:
 根据给定的权值,构造赫夫曼树,并计算WPL。
 根据给定的字母的频率,编赫夫曼码。
赫夫曼树及应用
一、基本术语
1.路径和路径长度
从树中一个结点到另一个结点之间的分支构
成这两个结点之间的路径,路径上的分支数目称
做路径长度。树的路径长度是从树根到每一结点
的路径长度之和。完全二叉树就是这种路径长度
最短的二叉树。
若规定根结点的层数为1,则从根结点到第L层结
点的路径长度为L-1。
路径和路径长度
1
3
2
4
树的路径长度
PL = 2*1+4*2+1*3 = 13
5
6
7
8
1
2
树的路径长度
PL = 2*1+2*2+2*3+1*4 = 16
3
4
5
6
8
7
赫夫曼树及应用
2.结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数
值,则这个数值称为该结点的权。
结点的带权路径长度为:从根结点到该结点
之间的路径长度与该结点的权的乘积。
a
结点b、e、g 的权分别为:
20、10和15
20 b
c
它们的带权路径长度分别为:
d e f
g
20×1=20
10×2=20
15
10
15×2=30
赫夫曼树及应用
3.树的带权路径长度(Weighted Path
Length, WPL)
树的带权路径长度规定为所有叶子结点的带
权路径长度之和,记为
WPL=
n
,
w
l
 i i
i 1
其中n 为叶子结点数目,wi为第i 个叶子
结点的权值,li 为第i 个叶子结点的路径长度。
例: 有4个结点,权值分别为7,5,2,4,
构造有4个叶子结点的二叉树。
c 2
a
7
4 d
a
7
b
5
b
5
c
2
d
4
WPL=7*2+5*2+2*2+4*2=36
WPL=7*3+5*3+2*1+4*2=46
7 a
5 b
WPL=7*1+5*2+2*3+4*3=35
2
c
d 4
赫夫曼树及应用
4.最优二叉树(赫夫曼树):带权路径长
度WPL最小的二叉树。
思考1:
有n个叶子结点的赫夫曼树唯一吗?
思考2:
赫夫曼树有何用处?
在赫夫曼树中,权值大的结点离根最近。
赫夫曼树及应用
二、哈夫曼树的构造
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。
n个权值分别设为 w1,w2,…,wn,则哈夫曼树的构造规则
为:
(1)将w1,w2,…,wn看成是有n 棵树的森林(每棵树仅有一个
结点);
(2)在森林中选出两个根结点的权值最小的树合并,作为一
棵新树的左、右子树,且新树的根结点权值为其左、右子
树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即
为我们所求得的哈夫曼树。
下面给出哈夫曼树的构造过程。假设给定的叶子结
点的权分别为1,5,7,3,则构造哈夫曼树过程如下:
1
5
7
5
3
(a)
7
4
(c)
4
1
(b)
16
9
1
7
5
7
9
4
3
1
5
3
(d)
3
赫夫曼树及应用
由构造过程可知:n 个权值构造哈夫曼树
需n-1次合并,每次合并,森林中的树
数目减1,最后森林中只剩下一棵树,
即为我们求得的哈夫曼树。
哈夫曼树构造程序
一棵有n个叶子结点的Huffman树有2n-1
个结点。
采用静态三叉链表存储结构。
结点类型及三叉链表类型定义为:
typedef struct
{ int weight;
int parent,lchild,rchild;
}HTNode, *HuffmanTree;
void HTCreate(HuffmanTree &HT, int *w, int n )
{
if(n<=1) return;
m=2*n-1;
HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
for(p=HT+1,i=1;i<=n;++i,++p,++w) *p={*w,0,0,0};
for(;i<=m;++i,++p) *p={0,0,0,0};
for(i=n+1;i<=m;++i) {
Select(HT,i-1,s1,s2);
HT[s1].parent=i;HT[s2].parent=i;
HT[i].lchild=s1;HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;}
}
赫夫曼树及应用
三、哈夫曼编码
在远程通讯中,要将待传字符转换成由二进制
组成的字符串:
设要传送的字符为:
若编码为:A—00
B—01
C—10
D---11
ABACCDA
00010010101100
哈夫曼编码
若将编码设计为长度不等的二进制编码,即让待传
字符串中出现次数较多的字符采用尽可能短的编码,
则转换的二进制字符串便可能减少。
设要传送的字符为:ABACCDA
若编码为: A—0
B—01
C—1
D---11
但: 0010
AACA ABA
重码
ABACCDA
001011110
哈夫曼编码
关键:要设计长度不等的编码,则必须使任一字
符的编码都不是另一个字符的编码的前缀。这种
编码称作前缀编码。
设要传送的字符为:
ABACCDA
若编码为 :A—0
B—110
C—10
D---111
0110010101110
0
A
采用二叉树设
计二进制前缀
编码
1
0
C
1
0
B
1
D
规定:
左分支用“0”表示;
右分支用“1”表示。
哈夫曼编码
译码过程:分解接收字符串:遇“0”向左,
遇“1”向右;一旦到达叶子结点,则译出一
个字符,反复由根出发,直到译码完成。
0
A
0110010101110
1
0
C
1
0
B
1
D
ABACCDA
哈夫曼编码
求Huffman编码:由叶子→根,若:
(1)从左分支下去,则分支为“0”;
(2)从右分支下去,则分支为“1”。
0
A
1
0
C
1
0
B
1
D
例:已知某系统在通讯时,只出现C,A,S,T,B
五种字符,它们出现的频率依次为2,4,2,3,3,
试设计Huffman编码。
W(权)={2,4,2,3,3},叶子结点个数n=5 。
14
0
3
T
0
6
1
3
B
0
4
A
1
8
0
2
C
由Huffman树得Huffman
编码为:
1
4
1
2
S
T
B
A
00
01 10
C
S
110 111
出现频率越大的字符,其Huffman编码越短。
哈夫曼编码
Typedef char * *HuffmanCode;
void HuffmanCode(HuffmanTree HT,
HuffmanCode &HC)
{
//从叶子到根逆向求每个字符的哈夫曼编码
HC=(HuffmanCode)malloc((n+1)*sizeof(char *));
cd=(char*)malloc(n*sizeof(char));
cd[n-1]=“\0”;
哈夫曼编码
int start,f,c;
for(int i=1;i<=n;++i)
{start=n-1;
for(c=i,f=HT[i].parent;f!=0;
c=f,f=HT[f].parent)
if(HT[f].lchild==c) cd[--start]=“0”;
else cd[--start]=“1”;
HC[i]=(char*)malloc((n-start)*sizeof(char));
strcpy(HC[i],&cd[start]);
}
free(cd);
}
作业4
1、给定30个字符组成的电文:
DDDDDAAABEEAAFCDAACABBC
CCBAADD
试为字符 A、B、C、D、E、F 设计哈夫曼(Huffman)
编码。
(1)画出相应的哈夫曼树;
(2)分别列出 A、B、C、D、E、F 的哈夫曼码;
(3)计算该树的带权路径长度WPL。
2、设二叉树以二叉链表存储,b指向根结点,
p所指结点为二叉树中任一给定的结点,
设计一个算法,输出从根结点到p所指结
点之间的路径。
3、设二叉树以二叉链表存储,设计一个算
法把二叉树的左右子树进行交换。
本章小结
学习内容及重点为:
 树的相关概念
 二叉树(重点)
 树、森林与二叉树关系(重点)
 二叉树的应用(重点)