第5章树和二叉树
5.1 内容概述
本章首先介绍树的定义和基本术语,然后介绍二叉树的定义、性质、存
储结构、遍历和线索二叉树,之后介绍树的存储结构、遍历、树(森林)与二
叉树的转换,最后介绍哈夫曼树及其应用。本章的知识结构如图5.1所示。
图5.1 第5章知识结构
考核要求:掌握树的定义和基本术语,掌握二叉树的定义和性质,掌握

1 10  数据结构学习与实验指导(C 语言版)(第4 版) 
满二叉树和完全二叉树的特点,掌握二叉树的遍历方法及算法实现,了解二叉树的线索化
过程及其算法实现,掌握树的存储、遍历以及树(森林)与二叉树的转换,掌握哈夫曼树的
构造方法。
重点难点:本章的重点是二叉树的遍历算法及其相关应用,难点是如何利用本章的
相关知识设计出实现某种功能的有效算法。
核心考点:二叉树的定义和性质,满二叉树和完全二叉树的特点,二叉树的存储结
构,二叉树的遍历过程,二叉树的线索化过程,哈夫曼树的构造方法,二叉树上递归算法的
设计。
5.2 典型题解析
5.2.1 二叉树的定义及其性质 
二叉树的特点是每个结点至多有两棵子树,即任何结点的度不大于2,而且二叉树的
子树有左右之分,其次序不能任意颠倒。二叉树的性质是计算或证明二叉树中某类结点
的个数或高度的基础。
【例5.1】 下列关于二叉树度的说法中正确的是( )。
A.二叉树的度为2 B.一棵二叉树的度可以小于2 
C.二叉树中至少有一个结点的度为2 D.二叉树中任何一个结点的度都为2 
解析:二叉树中每个结点至多有两棵子树,即二叉树中任何一个结点的度都不大于
2。由此可知,二叉树中可以没有度为2的结点,此时二叉树的度或者为1(结点数≥2), 
或者为0(结点数≤1)。
答案:B 
【例5.2】 下列叙述中正确的是( )。
A.二叉树是度为2的有序树
B.完全二叉树一定存在度为1的结点
C.对于有n个结点的二叉树,其高度为.log2n.+1 
D.深度为k的二叉树中的结点总数≤2k-1 
解析:二叉树是度不大于2的有序树,选项A 错误。当结点个数n为偶数时,完全二
叉树中有且仅有一个度为1的结点;当结点个数n为奇数时,完全二叉树中没有度为1的
结点,选项B错误。具有n个结点的完全二叉树的深度为.log2n.+1,选项C错误。深度
为k(k≥1)的二叉树上至多有2k-1个结点,选项D正确。
答案:D 
【例5.3】 若一棵二叉树有10个度为2的结点,5个度为1的结点,则度为0的结点
的个数是( )。
A.9 B.11 C.15 D.不确定
解析:任意一棵二叉树中,叶子结点的数目(用n0表示)总比度为2的结点的数目(用
n2表示)多一个,即n0=n2+1。

第5 章 树和二叉树 1 11 
答案:B 
【例5.4】 一个具有1025个结点的二叉树,其高度最小为( )。
A.10 B.11 C.12 D.不确定
解析:在结点数相同的所有形态的二叉树中,完全二叉树的深度最小,且具有n个结
点的完全二叉树的深度为.log2n.+1。由此可知,具有1025个结点的二叉树的最小深度
为.log21025.+1=10+1=11。
答案:B 
【例5.5】 高度为K的二叉树最多有( )个结点。
A.2K B.2K-1 C.2K-1 D.2K-1-1 
解析:在所有高度为K的二叉树中,满二叉树的结点数最多,结点数为2K-1。
答案:C 
【例5.6】 有n个结点且高度为n的二叉树的数目是多少? 
解析:有n个结点且高度为n的二叉树是从根结点到叶子结点的单枝树,单枝树的
数目由单枝树的形态所决定,单枝树的形态由叶子结点的位置所决定。深度为n的单枝
树的叶子结点共有2n-1种不同的位置,所以二叉树的数目是2n-1。由此可知,本题等价于
高度为n的满二叉树有多少个叶子结点。
【例5.7】 已知完全二叉树的第7层有10个叶子结点,则整个二叉树的结点数最多
是多少? 
解析:第7层共有27-1=64个结点,已知有10个叶子,其余54个结点均为分支结
点。由于本题求二叉树的结点数最多是多少,所以第8层上有108个叶子结点。该二叉
树的结点数最多为27-1+108=235。
【例5.8】 已知一棵满二叉树的结点数在20~40之间,此二叉树的叶子结点有多
少个?
解析:因为深度为k的满二叉树的结点数为2k-1,所以结点数在20~40之间的满
二叉树的深度为5。由此可知,其叶子结点数为25-1=16。
【例5.9】 假设高度为h的二叉树上只有度为0和2的结点,问此类二叉树中的结点
数可能达到的最大值和最小值各为多少? 
解析:当二叉树是满二叉树时,结点数达到最大值,结点数为2h-1;当二叉树除第一
层外其余每层均有两个结点时,结点数达到最小值,结点数为2h-1。
【例5.10】 已知一棵完全二叉树顺序存储在A[1..N]中,如何求出A[i]和A[j]的最
近的公共祖先? 
解析:根据顺序存储的完全二叉树的性质,编号为i的结点的双亲的编号是.i/2.,故
A[i]和A[j]的最近公共祖先可如下求出: 
while(i/2!=j/2) 
if(i>j) i=i/2; else j=j/2; 
退出while循环后,若i/2==0,则最近的公共祖先为根结点,否则最近的公共祖先是i/2 
(或j/2)。

1 12  数据结构学习与实验指导(C 语言版)(第4 版) 
5.2.2 二叉树的存储及其遍历
二叉树有顺序存储和链式存储(二叉链表)两种。二叉树遍历有先序遍历、中序遍历、
后序遍历和层次遍历四种。考查的重点是二叉树的递归遍历和非递归遍历的思想及算法
实现。考查方式主要有两种:一是对给定的二叉树,写出其先序、中序和后序遍历序列, 
或根据给定的遍历序列画出二叉树;二是算法设计,如建立给定遍历序列的存储结构、计
算满足条件的结点数、计算二叉树的高度(深度)、交换二叉树的左右子树、将叶子结点连
接成链表等。
【例5.11】 对二叉树的结点从1开始进行连续编号,要求每个结点的编号大于其左
右孩子的编号,同一结点的左右孩子中,其左孩子的编号小于其右孩子的编号,可采用
( )遍历实现编号。
A.先序 B.中序
C.后序 D.从根开始按层次
解析:由于每个结点的编号大于其左右孩子的编号,所以先遍历该结点的孩子,再遍
历该结点。在同一结点的左右孩子中,其左孩子的编号小于其右孩子的编号,所以先遍历
左孩子,再遍历右孩子。由此可知,遍历的顺序为左孩子→右孩子→根结点,故可采用后
序遍历实现编号。
答案:C 
【例5.12】 若一个二叉树的先序序列和后序序列正好相反,则该二叉树一定是
( )的二叉树。
A.空或只有一个结点 B.任一结点无左子树
C.高度等于其结点数 D.任一结点无右子树
解析:若二叉树的高度等于其结点数,则每层只有一个结点,其先序序列和后序序列
正好相反。选项A、B和D都是二叉树的高度等于其结点数的特殊情况,都不够全面。
答案:C 
【例5.13】 已知一棵二叉树的中序序列和后序序列,编写一个建立该二叉树的二叉
链表的存储结构的算法。已知一棵二叉树的中序遍历序列为CEIFGBADH,后序遍历序
列为EICBGAHDF,试画出该二叉树。
解析:首先建立根结点,根结点数据是后序遍历序列的最后一个元素,然后在中序序
列中查找根结点数据,根结点之前的元素在根结点的左子树上,根结点之后的元素在根结
点的右子树上,递归建立根结点的左右子树。 
typedef char ElemType; /*数据元素类型*/ 
typedef struct Node 
{ ElemType data; /*数据域*/ 
struct Node *lchild,*rchild; /*左右指针域,分别存储左右孩子的存储位置*/ 
}BitTree; 
BitTree *Creat(ElemType in[],ElemType post[],int l1,int h1,int l2,int h2) 
/*in 和post 存放中序序列和后序序列,l1,h1,l2,h2 分别是两个序列首尾结点的下标*/

第5 章 树和二叉树 1 13 
{ BitTree *t; 
int i; 
t=(BitTree *)malloc(sizeof(BitTree)); /*申请结点*/ 
t->data=post[h2]; /*后序遍历序列的最后一个元素是根结点数据*/ 
for(i=l1;i<=h1;i++) 
if(in[i]==post[h2]) break; /*在中序序列中查找根结点*/ 
if(i==l1) t->lchild=NULL; /*处理左子树*/ 
else t->lchild=Creat(in,post,l1,i-1,l2,l2+i-l1-1); 
if(i==h1) t->rchild=NULL; /*处理右子树*/ 
else t->rchild=Creat(in,post,i+1,h1,l2+i-l1,h2-1); 
return(t); 
}
中序遍历序列为CEIFGBADH、后序遍历序列为EICBGAHDF的二叉树的生成过
程如图5.2所示。
图5.2 二叉树的生成过程
【例5.14】 有n个结点的完全二叉树存放在一维数组A[1..n]中,试据此建立该二
叉树的二叉链表。
解析:完全二叉树按层次存储在一维数组A 中,编号从1到n。在数组A 中从下标
为1的单元开始依次取元素值,同时建立新结点,若结点的孩子存在,则递归建立该结点
的子树,否则子树为空。 
BitTree *Creat(ElemType A[],int n,int i) 
/*n 是结点数,i 是数组A 的下标,调用时i=1*/ 
{ BitTree *T; /*BitTree 的定义见例5.13*/ 
if(i<=n) 
{ T=(BitTree *)malloc(sizeof(BitTree)); /*建立新结点*/ 
T->data=A[i]; /*新结点的数据域值*/ 
if(2*i>n) T->lchild=NULL; /*左子树为空*/ 
else T->lchild=Creat(A,n,2*i); /*左子树不为空,递归建立左子树*/ 
if(2*i+1>n) T->rchild=NULL; /*右子树为空*/ 
else T->rchild=Creat(A,n,2*i+1); /*右子树不为空,递归建立右子树*/ 
}

1 14  数据结构学习与实验指导(C 语言版)(第4 版) 
return T; 
}
【例5.15】 已知二叉树按二叉链表形式存储,编写算法,判断给定的二叉树是否为完
全二叉树。
解析:深度为k、具有n个结点的完全二叉树的每个结点都与深度为k的满二叉树中
编号从1至n的结点一一对应。从根结点开始,按层次扫描二叉树,若当前结点的左子树
不为空且前面已扫描结点的左右子树都不为空,或当前结点及前面已扫描的结点的左右
子树都不为空,则继续扫描下一个结点;否则不是完全二叉树,结束扫描。 
#define MAXSIZE 20 /*最多结点数*/ 
int Judge(BitTree *bt) /*BitTree 的定义见例5.13*/ 
{ int tag=0,front=0,rear=0; 
BitTree *p=bt, *Q[MAXSIZE]; /*Q 是队列,元素是二叉树结点指针*/ 
if(p==NULL) return 1; 
Q[rear++]=p; /*根结点指针入队*/ 
while(front!=rear) 
{ p=Q[front++]; /*出队*/ 
if(p->lchild&&! tag) Q[rear++]=p->lchild; /*左孩子入队*/ 
else if(p->lchild) return 0; /*前边已有结点为空,本结点不为空*/ 
else tag=1; / * 首次出现结点为空*/ 
if(p->rchild&&! tag) Q[rear++]=p->rchild; /*右孩子入队*/ 
else if(p->rchild) return 0; 
else tag=1; 
} 
return 1; 
}
【例5.16】 设T是一棵满二叉树,按顺序存储方式存储,编写将T 的先序遍历序列
转换为后序遍历序列的递归算法。
解析:先序序列的第一个元素是后序序列的最后一个元素,剩余元素的前一半在左
子树上,后一半在右子树上。用递归的方法将左子树的先序序列转换为后序序列,将右子
树的先序序列转换为后序序列。对一般二叉树,仅根据一个先序、中序或后序遍历序列不
能确定另一个遍历序列。但对于满二叉树,任一结点的左右子树均含有数量相等的结点, 
根据此性质,可将任一遍历序列转换为另一遍历序列,即任一遍历序列均可确定一棵二
叉树。 
typedef char ElemType; /*数据元素类型*/ 
void PreToPost(ElemType pre[],ElemType post[],int l1,int h1,int l2,int h2) 
/*l1,h1,l2,h2 分别是序列初始结点和最后结点的下标*/ 
{ int half; 
if(h1>=l1) 
{ post[h2]=pre[l1]; /*根结点*/ 
half=(h1-l1)/2; /*左子树或右子树的结点数*/

第5 章 树和二叉树 1 15 
PreToPost(pre,post,l1+1,l1+half,l2,l2+half-1); 
/*将左子树先序序列转换为后序序列*/ 
PreToPost(pre,post,l1+half+1,h1,l2+half,h2-1); 
/*将右子树先序序列转换为后序序列*/ 
} 
}
【例5.17】 已知二叉树按二叉链表方式存储,设计一个算法,把二叉树的叶子结点按
从左到右的顺序连接成一个单链表,表头指针为head。连接时用叶子结点的右指针域存
放单链表指针。
解析:采用中序递归遍历的方法查找叶子结点,设置前驱结点指针pre,初始为空。
第一个叶子结点由指针head指向,当遍历到叶子结点时,其前驱的rchild指针指向它,最
后叶子结点的rchild为空。 
BitTree *head,*pre=NULL; /*head 为头指针,pre 为尾指针*/ 
LinkLeaf(BitTree *bt) /*BitTree 的定义见例5.13*/ 
{ if(bt) 
{ LinkLeaf(bt->lchild); /*中序遍历左子树*/ 
if(bt->lchild==NULL&&bt->rchild==NULL) /*叶子结点*/ 
if(pre==NULL) { head=bt; pre=bt;} /*处理第一个叶子结点*/ 
else { pre->rchild=bt; pre=bt; } /*将叶子结点链入链表*/ 
LinkLeaf(bt->rchild); /*中序遍历右子树*/ 
pre->rchild=NULL; /*设置链表尾*/ 
} 
}
【例5.18】 已知二叉树采用二叉链表存储,编写非递归算法,交换二叉树的左右
子树。解
析:设置一个栈stack存放还没有交换过的结点,它的栈顶指针为top。交换左右
子树的算法如下。
(1)把根结点放入栈。
(2)当栈不为空时,取出栈顶元素,交换它的左右子树,并把它的左右子树的根结点
分别入栈。
(3)重复操作(2),直到栈为空为止。 
#define MAXSIZE 20 /*最多结点数*/ 
void Exchange(BitTree *t) /*BitTree 的定义见例5.13*/ 
{ BitTree *r,*p,*stack[MAXSIZE]; 
int top=0; 
stack[top++]=t; /*根结点入栈*/ 
while(top>0) /*栈不为空*/ 
{ p=stack[--top]; /*出栈*/ 
if(p) 
{ r=p->lchild; /*交换*/

1 16  数据结构学习与实验指导(C 语言版)(第4 版) 
p->lchild=p->rchild; 
p->rchild=r; 
stack[top++]=p->lchild; /*左子树根结点入栈*/ 
stack[top++]=p->rchild; /*右子树根结点入栈*/ 
} 
} 
}
【例5.19】 假设一棵完全二叉树使用顺序存储结构存储在数组bt[1..n]中,写出进
行非递归先序遍历的算法。
解析:完全二叉树按顺序存储结构存储时,双亲与孩子结点的下标之间有确定关系。
对顺序存储结构的完全二叉树进行遍历与二叉链表类似。在顺序存储结构下,通过结点
下标是否大于n判断完全二叉树是否为空。 
typedef char ElemType; /*数据元素类型*/ 
#define MAXSIZE 20 /*最多结点数*/ 
void PreOrder(ElemType bt[],int n) 
{ int top=0,s[MAXSIZE]; /*top 是栈s 的栈顶指针*/ 
int i=1; 
while(i<=n||top>0) 
{ while(i<=n) 
{ printf("%3c",bt[i]); /*访问根结点*/ 
if(2*i+1<=n) s[top++]=2*i+1; /*右孩子的下标进栈*/ 
i=2*i; /*沿左孩子向下*/ 
} 
if(top>0) i=s[--top]; 
} 
}
【例5.20】 已知二叉树T采用二叉链表存储,设计算法,返回二叉树T 的先序序列
的最后一个结点的指针,要求采用非递归形式,且不允许使用栈。
解析:若二叉树有右子树,则二叉树先序序列的最后一个结点是右子树中最右下的
叶子结点;若二叉树无右子树,仅有左子树,则二叉树先序序列的最后一个结点是左子树
最右下的叶子结点;若二叉树无左右子树,则根结点是二叉树先序序列的最后一个结点。 
BitTree *LastNode(BitTree *bt) /*BitTree 的定义见例5.13*/ 
{ BitTree *p=bt; 
if(bt==NULL) return NULL; 
else 
while(p) 
if(p->rchild) p=p->rchild; /*若右子树不为空,则沿右子树向下*/ 
else if(p->lchild) p=p->lchild; /*若右子树为空,左子树不为空,则沿左子树向下*/ 
else return p; /*p 即为所求*/ 
}

第5 章 树和二叉树 1 17 
5.2.3 线索二叉树
线索二叉树分为先序线索二叉树、中序线索二叉树和后序线索二叉树三种。考查的
重点是建立和遍历线索二叉树的算法。考查的方式主要有两种:一是画出给定二叉树的
线索树,二是算法设计,如建立线索二叉树、在线索二叉树中查找给定条件的结点和在线
索二叉树中插入结点等。
【例5.21】 线索二叉树是一种( )结构。
A.逻辑 B.逻辑和存储 C.物理 D.线性
解析:当用二叉链表作为二叉树的存储结构时,因为每个结点中只有指向左右孩子
结点的指针域,所以从任一结点出发只能直接找到该结点的左右孩子,一般情况下无法直
接找到该结点在某种遍历序列中的前驱和后继结点。但是,在n个结点的二叉链表中含
有n+1个空指针域,因此可以利用这些空指针域存放指向结点在某种遍历次序下的前驱
或后继结点的指针。这种附加的指针称为线索,加上线索的二叉链表称为线索链表,相应
的二叉树称为线索二叉树。由此可知,线索二叉树是一种物理结构。
答案:C 
【例5.22】 一棵左子树为空的二叉树在先序线索化后,其空链域的个数为( )。
A.不确定 B.0 C.1 D .2 
解析:一棵左子树为空的二叉树在先序线索化后,因为第一个遍历的结点没有左孩
子且没有前驱,最后一个遍历的结点没有右孩子且没有后继,所以空链域的个数为2。
答案:D 
【例5.23】 若X是二叉中序线索树中的一个有左孩子的结点,且X 不为根结点,则
X的前驱为( )。
A.X的双亲 B.X的右子树中最左的结点
C.X的左子树中最右的结点 D.X的左子树中最右的叶子结点
解析:由中序遍历的过程可知,访问X的左子树的最右结点后访问X,X的左子树的
最右结点的右链域是线索,指向其后继X。由此可知,X 的前驱为X 的左子树中最右的
结点。答
案:C 
【例5.24】 设t是一棵按后序遍历方式构成的线索二叉树的根结点指针,试设计一
个非递归的算法,把一个地址为x的新结点插入t树,已知地址为y的结点右孩子作为结
点x的右孩子,并使插入后的二叉树仍为后序线索二叉树。
解析:在线索二叉树上插入结点,破坏了与被插入结点的线索,因此在插入结点时必
须修复线索。因为是后序线索树,所以在结点y的右侧插入结点x时要区分结点y有无
左子树的情况。 
typedef char ElemType; /*数据元素类型*/ 
typedef struct node 
{ ElemType data; /*数据域*/ 
struct node *lchild; /*左链域*/

1 18  数据结构学习与实验指导(C 语言版)(第4 版) 
struct node *rchild; /*右链域*/ 
int ltag; /*左链域信息标志*/ 
int rtag; /*右链域信息标志*/ 
}BiThrTree; 
void Insert(BiThrTree *t,BiThrTree *y,BiThrTree *x) 
{ BiThrTree *p; 
if(y->ltag==0) /*y 有左孩子*/ 
{ p=y->lchild; 
if(p->rtag==1) p->rchild=x; /*x 是y 的左孩子的后序后继*/ 
x->ltag=1; x->lchild=p; /*x 的左线索是y 的左孩子*/ 
} 
else /*y 无左孩子*/ 
{ x->ltag=1; x->lchild=y->lchild; /*y 的左线索成为x 的左线索*/ 
if(y->lchild->rtag==1) /*若y 的后序前驱的右标记为1*/ 
y->lchild->rchild=x; /*则将y 的后序前驱的后继改为x*/ 
} 
x->rtag=1; 
x->rchild=y; 
y->rtag=0; 
y->rchild=x; /*x 作为y 的右子树*/ 
}
【例5.25】 编写算法,在中序线索二叉树中查找值为x的结点的后继结点,并返回该
后继结点的指针。
解析:先在带头结点的中序线索二叉树T中查找值为x的结点,然后在中序线索二
叉树T中查找值为x的结点的后继结点。 
BiThrTree *Search(BiThrTree *T,ElemType x) /*BiThrTree 的定义见例5.24*/ 
/*在带头结点的中序线索二叉树T 中查找值为x 的结点*/ 
{ BiThrTree *p; 
p=T->lchild; /*p 指向二叉树的根结点*/ 
while(p!=T) 
{ while(p->ltag==0&&p->data!=x) p=p->lchild; 
if(p->data==x) return p; 
while(p->rtag==1&&p->rchild!=T) 
{ p=p->rchild; if(p->data==x) return p;} 
p=p->rchild; 
} 
}B
iThrTree *AfterNode(BiThrTree *T,ElemType x) 
/*在中序线索二叉树T 中查找值为x 的结点的后继结点*/ 
{ BiThrTree *p,*q; 
p=Search(T,x); /*在T 中查找给定值为x 的结点,由p 指向*/ 
if(p->rtag==1)