数据结构教程第5章 树和二叉树.docx
- 文档编号:11858549
- 上传时间:2023-04-06
- 格式:DOCX
- 页数:27
- 大小:203.53KB
数据结构教程第5章 树和二叉树.docx
《数据结构教程第5章 树和二叉树.docx》由会员分享,可在线阅读,更多相关《数据结构教程第5章 树和二叉树.docx(27页珍藏版)》请在冰豆网上搜索。
数据结构教程第5章树和二叉树
第5章树和二叉树
本章主要介绍下列内容(教材的第6章)
1.树的定义和存储结构
2.二叉树的定义、性质、存储结构
3.二叉树的遍历、线索算法
4.树和二叉树的转换
5.哈夫曼树及其应用
课时分配:
第1、2节两个学时,第3节四个学时,第4、5节各两个学时,上机六个学时
重点、难点:
二叉树的遍历、线索算法、哈夫曼树及其应用
第一节树
1.树的定义和基本运算
1.1定义
树是一种常用的非线性结构。
我们可以这样定义:
树是n(n≥0)个结点的有限集合。
若n=0,则称为空树;否则,有且仅有一个特定的结点被称为根,当n>1时,其余结点被分成m(m>0)个互不相交的子集T1,T2,...,Tm,每个子集又是一棵树。
由此可以看出,树的定义是递归的。
结点数据元素的内容及其指向其子树根的分支统称为结点。
结点的度结点的分支数。
终端结点(叶子)度为0的结点。
非终端结点度不为0的结点。
结点的层次树中根结点的层次为1,根结点子树的根为第2层,以此类推。
树的度树中所有结点度的最大值。
树的深度树中所有结点层次的最大值。
有序树、无序树如果树中每棵子树从左向右的排列拥有一定的顺序,不得互换,则称为有序树,否则称为无序树。
森林是m(m≥0)棵互不相交的树的集合。
在树结构中,结点之间的关系又可以用家族关系描述,定义如下:
孩子、双亲结点子树的根称为这个结点的孩子,而这个结点又被称为孩子的双亲。
子孙以某结点为根的子树中的所有结点都被称为是该结点的子孙。
祖先从根结点到该结点路径上的所有结点。
兄弟同一个双亲的孩子之间互为兄弟。
堂兄弟双亲在同一层的结点互为堂兄弟。
1.2树的基本运算
常用操作:
(1)构造一个树CreateTree(T)
(2)清空以T为根的树ClearTree(T)
(3)判断树是否为空TreeEmpty(T)
(4)获取给定结点的第i个孩子Child(T,linklist,i)
(5)获取给定结点的双亲Parent(T,linklist)
(6)遍历树Traverse(T)
对树遍历的主要目的是将非线性结构通过遍历过程线性化,即获得一个线性序列。
树的遍历顺序有两种,一种是先序遍历,即先访问根结点,然后再依次用同样的方法访问每棵子树;另一种是后序遍历,即先依……
2.树的存储结构
2.1双亲表示法
树的双亲表示法主要描述的是结点的双亲关系
类型定义:
#defineMAX_TREE_LINKLIST_SIZE100
typedefstruct{
TElemtypeinfo;
intparent;
}ParentLinklist;
typedefstruct{
ParentLinklistelem[MAX_TREE_LINKLIST_SIZE];
intn;//树中当前的结点数目
}ParentTree;
这种存储方法的特点是寻找结点的双亲很容易,但寻找结点的孩子比较困难。
算法实现举例:
intParent(ParentTreeT,intlinklist)
{if(linklist<0||linklist>=T.n)return-2;
elsereturnT.elem[linklist].parent;
}
2.2孩子表示法
孩子表示法主要描述的是结点的孩子关系。
由于每个结点的孩子个数不定,所以利用链式存储结构更加适宜。
举例如下图(a):
在C语言中,这种存储形式定义如下:
#defineMAX_TREE_LINKLIST_SIZE10
typedefstructChildLinklist
{intchild;//该孩子结点在一维数组中的下标值
structChileLinklist*next;//指向下一个孩子结点
}CLinklist;
typedefstruct{
Elemtypeinfo;//结点信息
CLinklist*firstchild;//指向第一个孩子结点的指针
}TLinklist;
typedefstruct
{TLinklistelem[MAX_TREE_LINKLIST_SIZE];
intn,root;//n为树中当前结点的数目,root为根结点在一维数组中的位置
}ChildTree;
这种存储结构的特点是寻找某个结点的孩子比较容易,但寻找双亲比较麻烦,所以,在必要的时候,可以将双亲表示法和孩子表示法结合起来,即将一维数组元素增加一个表示双亲结点的域parent,用来指示结点的双亲在一维数组中的位置。
获取给定结点第i个孩子的操作算法实现:
intChild(ChildTreeT,intlinklist,inti)
{
if(linklist<0||linklist>=T.n)return-2;
p=T.elem[linklist].firstchild;j=1;
while(p&&j!
=i){p=p->next;j++;}
if(!
p)return-2;elsereturnp->child;
}
2.3孩子兄弟表示法
孩子兄弟表示法也是一种链式存储结构。
它通过描述每个结点的一个孩子和兄弟信息来反映结点之间的层次关系,其结点结构为:
firstchild
elem
nextsibling
其中,firstchild为指向该结点第一个孩子的指针,nextsibling该结点的下一个兄弟的指针,elem是数据元素内容。
举例如下图:
在C语言中,这种存储形式定义如下:
typedefstructCSLinklist{
Elemtypeelem;
structCSLinklist*firstchild,*nextsibling;
}CSLinklist,*CSTree;
voidAllChild(CSTreeT,CSTreep)//输出树中p指针所指结点的所有孩子信息
{
q=p->fisrtchild;
while(q){printf("%c",q->elem);q=q->nextsibling;}
}
第二节二叉树
1.二叉树的定义和基本运算
1.1定义
定义:
二叉树是另一种树形结构。
它与树形结构的区别是:
(1)每个结点最多有两棵子树;
(2)子树有左右之分。
二叉树也可以用递归的形式定义。
即:
二叉树是n(n≥0)个结点的有限集合。
当n=0时,称为空二叉树;当n>0时,有且仅有一个结点为二叉树的根,其余结点被分成两个互不相交的子集,一个作为左子集,另一个作为右子集,每个子集又是一个二叉树。
二叉树的5种形态
1.2二叉树的基本运算(略见教材)
(1)构造一棵二叉树CreateBTree(BT)
(2)清空以BT为根的二叉树ClearBTree(BT)
(3)判断二叉树是否为空BTreeEmpty(BT)
(4)获取给定结点的左孩子和右孩子LeftChild(BT,linklist),
RightChild(BT,linklist)
(5)获取给定结点的双亲Parent(BT,linklist)
(6)遍历二叉树Traverse(BT)
2.二叉树的性质
二叉树具有下列5个重要的性质。
(证明见教材或《离散数学》,此处略)
【性质1】在二叉树的第i层上最多有2i-1个结点(i≥1)。
【性质2】深度为K的二叉树最多有2K-1个结点(K≥1)。
【性质3】对于任意一棵二叉树BT,如果度为0的结点个数为n0,度为2的结点个数为n2,则n0=n2+1。
证明:
假设度为1的结点个数为n1,结点总数为n,B为二叉树中的分支数。
因为在二叉树中,所有结点的度均小于或等于2,所以结点总数为:
n=n0+n1+n2
(1)
再查看一下分支数。
在二叉树中,除根结点之外,每个结点都有一个从上向下的分支指向,所以,总的结点个数n与分支数B之间的关系为:
n=B+1。
又因为在二叉树中,度为1的结点产生1个分支,度为2的结点产生2个分支,所以分支数B可以表示为:
B=n1+2n2。
将此式代入上式,得:
n=n1+2n2+1
(2)
用
(1)式减去
(2)式,并经过调整后得到:
n0=n2+1。
满二叉树:
如果一个深度为K的二叉树拥有2K-1个结点,则将它称为满二叉树。
完全二叉树:
有一棵深度为h,具有n个结点的二叉树,若将它与一棵同深度的满二叉树中的所有结点按从上到下,从左到右的顺序分别进行编号,且该二叉树中的每个结点分别与满二叉树中编号为1至n的结点位置一一对应,则称这棵二叉树为完全二叉树。
【性质4】具有n个结点的完全二叉树的深度为
+1。
其中,
的结果是不大于log2n的最大整数。
【性质5】见教材。
3.二叉树的存储结构
二叉树也可以采用两种存储方式:
顺序存储结构和链式存储结构。
3.1顺序存储结构
这种存储结构适用于完全二叉树。
其存储形式为:
用一组连续的存储单元按照完全二叉树的每个结点编号的顺序存放结点内容。
下面是一棵二叉树及其相应的存储结构
在C语言中,这种存储形式的类型定义如下所示:
#defineMAX_TREE_LINKLIST_SIZE100
typedefstruct
{Elemtypeelem[MAX_TREE_LINKLIST_SIZE];//根存储在下标为1的数组单元中
intn;//当前完全二叉树的结点个数
}QBTree;
这种存储结构的特点是空间利用率高、寻找孩子和双亲比较容易。
下面我们给出完全二叉树在这种存储形式下的操作算法。
(1)构造一棵完全二叉树
voidCreateBTree(QBTree*BT,Elemtypeelem[],intn)
{
if(n>=MAX_TREE_LINKLIST_SIZE)n=MAX_TREE_LINKLIST_SIZE-1;
for(i=1;i<=n;i++)
BT->elem[i]=elem[i];
BT->n=n;
}
(2)获取给定结点的左孩子
intLeftCHild(QBTreeBT,intlinklist)
{
if(2*linklist>BT.n)return0;
elsereturn2*linklist;
}
RightChild(BT,linklist)与这个操作类似,学生可试着自行完成。
(3)获取给定结点的双亲
intParent(QBTreeBT,intlinklist)
{
if(1<=linklist&&linklist<=BT.n)returni/2;
elsereturn-1;
}
3.2链式存储结构
在顺序存储结构中,利用编号表示元素的位置及元素之间孩子或双亲的关系,因此对于非完全二叉树,需要将空缺的位置用特定的符号填补,若空缺结点较多,势必造成空间利用率的下降。
在这种情况下,就应该考虑使用链式存储结构。
常见的二叉树结点结构如下所示:
其中,Lchild和Rchild是分别指向该结点左孩子和右孩子的指针,elem是数据元素的内容。
在C语言中的类型定义为:
typedefstructBTLinklist{
Elemtypeelem;
structBTLinklist*Lchild,*Rchlid;
}BTLinklist,*BTree;
下面是一棵二叉树及相应的链式存储结构
这种存储结构的特点是寻找孩子结点容易,双亲比较困难。
因此,若需要频繁地寻找双亲,可以给每个结点添加一个指向双亲结点的指针域,其结点结构如下所示。
第三节遍历二叉树和线索二叉树
二叉树是一种非线性的数据结构,在对它进行操作时,总是需要逐一对每个数据元素实施操作,这样就存在一个操作顺序问题,由此提出了二叉树的遍历操作。
所谓遍历二叉树就是按某种顺序访问二叉树中的每个结点一次且仅一次的过程。
这里的访问可以是输出、比较、更新、查看元素内容等等各种操作。
二叉树的遍历方式分为两大类:
一类按根、左子树和右子树三个部分进行访问;另一类按层次访问。
下面我们将分别进行讨论。
1.按根、左子树和右子树三部分进行遍历
遍历二叉树的顺序存在下面6种可能:
TLR(根左右),TRL(根右左)
LTR(左根右),RTL(右根左)
LRT(左右根),RLT(右左根)
其中,TRL、RTL和RLT三种顺序在左右子树之间均是先右子树后左子树,这与人们先左后右的习惯不同,因此,往往不予采用。
余下的三种顺序TLR、LTR和LRT根据根访问的位置不同分别被称为先序遍历、中序遍历和后序遍历。
(1)先序遍历
若二叉树为空,则结束遍历操作;否则
访问根结点;
先序遍历左子树;
先序遍历右子树。
(2)中序遍历若二叉树为空,则结束遍历操作;否则
中序遍历左子树;
访问根结点;
中序遍历右子树。
(3)后序遍历
若二叉树为空,则结束遍历操作;否则
后序遍历左子树;
后序遍历右子树;
访问根结点。
下面是一棵二叉树及其经过三种遍历得到的相应序列
下面我们再给出两种遍历二叉树的方法:
(1)对一棵二叉树中序遍历时,若我们将二叉树严格地按左子树的所有结点位于根结点的左侧,右子树的所有结点位于根右侧的形式绘制,就可以对每个结点做一条垂线,映射到下面的水平线上,由此得到的顺序就是该二叉树的中序遍历序列
(2)任何一棵二叉树都可以将它的外部轮廓用一条线绘制出来,我们将它称为二叉树的包线,这条包线对于理解二叉树的遍历过程很有用。
由此可以看出:
(1)遍历操作实际上是将非线性结构线性化的过程,其结果为线性序列,并根据采用的遍历顺序分别称为先序序列、中序序列或后序序列;
(2)遍历操作是一个递归的过程,因此,这三种遍历操作的算法可以用递归函数实现。
(1)先序遍历递归算法
voidPreOrder(BTreeBT)
{
if(BT){Visit(BT);
PreOrder(BT->Lchild);
PreOrder(BT->Rchild);}
}
(2)中序遍历递归算法
voidInOrder(BTreeBT)
{
if(BT){
InOrder(BT->Lchild);
Visit(BT);
InOrder(BT->Rchild);
}
}
(3)后序遍历递归算法
voidPostOrder(BTreeBT)
{
if(BT){
PostOrder(BT->Lchild);
PostOrder(BT->Rchild);
Visit(BT);
}
}
2.按层次遍历二叉树
实现方法为从上层到下层,每层中从左侧到右侧依次访问每个结点。
下面我们将给出一棵二叉树及其按层次顺序访问其中每个结点的遍历序列。
voidLevelOreder(QBTreeBT)
{
for(i=1;i<=BT.n;i++)
if(BT.elem[i]!
='#')Visite(BT.elem[i]);
}
二叉树用链式存储结构表示时,按层遍历的算法实现
访问过程描述如下:
访问根结点,并将该结点记录下来;
若记录的所有结点都已处理完毕,则结束遍历操作;否则重复下列操作。
取出记录中第一个还没有访问孩子的结点,若它有左孩子,则访问左孩子,并将记录下来;若它有右孩子,则访问右孩子,并记录下来。
在这个算法中,应使用一个队列结构完成这项操作。
所谓记录访问结点就是入队操作;而取出记录的结点就是出队操作。
这样一来,我们的算法就可以描述成下列形式:
(1)访问根结点,并将根结点入队;
(2)当队列不空时,重复下列操作:
从队列退出一个结点;
若其有左孩子,则访问左孩子,并将其左孩子入队
若其有右孩子,则访问右孩子,并将其右孩子入队;
voidLevelOrder(BTree*BT)
{
if(!
BT)exit;
InitQueue(Q);p=BT;//初始化
Visite(p);EnQueue(&Q,p);//访问根结点,并将根结点入队
while(!
QueueEmpty(Q)){//当队非空时重复执行下列操作
DeQueue(&Q,&p);//出队
if(!
p->Lchild){Visite(p->Lchild);EnQueue(&Q,p->Lchild);//处理左孩子
if(!
p->Rchild){Visite(p->Rchild);EnQueue(&Q,p->Rchild);//处理右孩子
}
}
3.二叉树的典型操作算法(线索化根据时间多少按教材略讲)
(1)输入一个二叉树的先序序列,构造这棵二叉树
为了保证唯一地构造出所希望的二叉树,在键入这棵树的先序序列时,需要在所有空二叉树的位置上填补一个特殊的字符,比如,'#'。
在算法中,需要对每个输入的字符进行判断,如果对应的字符是'#',则在相应的位置上构造一棵空二叉树;否则,创建一个新结点。
整个算法结构以先序遍历递归算法为基础,二叉树中结点之间的指针连接是通过指针参数在递归调用返回时完成。
BTreePre_Create_BT()
{
getch(ch);
if(ch=='#')returnNULL;//构造空树
else{BT=(BTree)malloc(sizeof(BTLinklist));//构造新结点
BT->data=ch;
BT->lchild=Pre_Create_BT();//构造左子树
BT->rchild=Pre_Create_BT();//构造右子树
returnBT;
}
}
(2)计算一棵二叉树的叶子结点数目
这个操作可以使用三种遍历顺序中的任何一种,只是需要将访问操作变成判断该结点是否为叶子结点,如果是叶子结点将累加器加1即可。
下面这个算法是利用中序遍历实现的。
voidLeaf(BTreeBT,int*count)
{
if(BT){
Leaf(BT->child,&count);//计算左子树的叶子结点个数
if(BT->lchild==NULL&&BT->rchild==NULL)(*count)++;
Leaf(BT->rchild,&count);//计算右子树的叶子结点个数
}
}
(3)交换二叉树的左右子树
许多操作可以利用三种遍历顺序的任何一种,只是某种遍历顺序实现起来更加方便一些。
而有些操作则不然,它只能使用其中的一种或两种遍历顺序。
将二叉树中所有结点的左右子树进行交换这个操作就属于这类情况。
voidchange_left_right(BTreeBT)
{
if(BT){
change_left_right(BT->lchild);
change_left_right(BT->rchild);
BT->lchild<->BT->rchild;
}
}
(4)求二叉树的高度
这个操作使用后序遍历比较符合人们求解二叉树高度的思维方式。
首先分别求出左右子树的高度,在此基础上得出该棵树的高度,即左右子树较大的高度值加1。
inthight(BTreeBT)
{//h1和h2分别是以BT为根的左右子树的高度
if(BT==NULL)return0;
else{
h1=hight(BT->lchild);
h2=hight(BT->right);
returnmax{h1,h2}+1;
}
}
二叉树建立和遍历的程序补充:
/*binarybtcreat.c递归*/
#include
#include
#include
typedefcharelemtype;
typedefstructbitnode
{elemtypedata;
structbitnode*lchild,*rchild;}bitnode,*bitree;
voidcreat(bitree&t)
{charch;
scanf("%c",&ch);
if(ch=='')t=NULL;
else{/*t=(bitnode*)malloc(sizeof(bitnode));*/
t=newbitnode;
t->data=ch;
creat(t->lchild);
creat(t->rchild);}
}
voidpreorder(bitreet)
{if(t){cout<
voidinorder(bitreet)
{if(t){inorder(t->lchild);cout<
voidpostorder(bitreet)
{if(t){postorder(t->lchild);postorder(t->rchild);cout<
main()
{bitreebt;
creat(bt);
preorder(bt);cout< inorder(bt);cout< postorder(bt);cout< } /*binarybtcreat1.c*非递归/ #include #include #include typedefcharelemtype; typedefstructbitnode {elemtypedata; structbitnode*lchild,*rchild;}bitnode,*bitree; typedefstructsnode {bitreept; structsnode*next;}snode,*slink; typedefstructqnode {bitreept; structqnode*next;}qnode,*qlink; voidpush(slink&s,bitreet) {slinks1;s1=newsnode;s1->pt=t;s1->next=s;s=s1;} voidpop(slink&s,bitree&t) {slinks1;if(s){t=s->pt;s1=s;s=s->next;deletes1;}} voideq(qlink&f,qlink&e,bitreet) {qlinks1;s1=newqnode;s1->pt=t;s1->next=NULL; if(e){e->next=s1;e=s1;}elsef=e=s1;} voiddq(qlink&f,qlink&e,bitree&t) {qlinks1; if(f){t=f->pt;s1=f;f=f->next;deletes1;if(! f)e=f;}} v
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构教程第5章 树和二叉树 数据结构 教程 二叉