第六章西北大学树和二叉树.ppt
第6章 树和二叉树,6.1 树的定义与基本术语6.2 二叉树6.3 二叉树的遍历与线索化6.4 树、森林和二叉树的关系6.5 哈夫曼树及其应用6.6 总结与提高,6.1 树的定义与基本术语,1树的基本概念2树的图解表示法 3树的相关术语 4树的抽象数据类型,6.1 树的定义与基本术语,树定义:是n(n0)个结点的有限集合T。当n=0时,称为空树;当n0时,该集合满足如下条件:,(1)其中必有一个称为根(root)的特定结点,它没有直接前驱,但有零个或多个直接后继。,(2)其余n-1个结点可以划分成m(m0)个互不相交的有限集T1,T2,T3,Tm,其中Ti又是一棵树,称为根root的子树。每棵子树的根结点有且仅有一个直接前驱,可有零个或多个直接后继。,1树的基本概念,例如:一棵树的逻辑结构图(6.1)为:,从图中可以看出它好象一棵倒栽的树。,2树的图解表示法,1)倒置树结构(树形表示法)图6.1,2)文氏图表示法(嵌套集合形式)图6.2,3)广义表形式(嵌套扩号表示法),4)凹入表示法 图6.3,图6.2 文氏图表示法,图6.3 凹入表示法,3.树的相关术语:,结点:包含一个数据元素及若干指向其它结点的分支信息。,结点的度:一个结点的子树个数称为此结点的度。,叶结点:度为0的结点,即无后继的结点,也称为终端结点。,分支结点:度不为0的结点,也称为非终端结点。,结点的层次:从根结点开始定义,根结点的层次为1,根的直接后继的层次为2,依此类推。,结点的层次编号:将树中的结点按从上层到下层、同层从左到右的次序排成一个线性序列,依次给它们编以连续的自然数。,树的度:树中所有结点的度的最大值。,树的高度(深度):树中所有结点的层次的最大值。,有序树:在树T中,如果各子树Ti之间是有先后次序的,则称为有序树。,森林:m(m0)棵互不相交的树的集合。将一棵非空树的根结点删去,树就变成一个森林;反之,给森林增加一个统一的根结点,森林就变成一棵树。,同构:对两棵树,通过对结点适当地重命名,就可以使两棵树完全相等(结点对应相等,对应结点的相关关系也像等),则称这两棵树同构。,双亲结点:一个结点的直接前驱称为该结点的双亲结点。上图中A是B、C的双亲。,兄弟结点:同一双亲结点的孩子结点之间互称兄弟结点。上图中的结点H、I、J互为兄弟结点。,祖先结点:一个结点的祖先结点是指从根结点到该结点的路径上的所有结点。如结点K的祖先结点是A、B、E。,子孙结点:一个结点的直接后继和间接后继称为该结点的子孙结点。如结点D的子孙是H、I、J、M。,孩子结点:一个结点的直接后继称为该结点的孩子结点。如上图的B、C是A的孩子。,我们常常借助人类家族树的术语,以便于直观理解结点间的层次关系。,堂兄弟:父亲是兄弟关系或堂兄关系的结点称为堂兄弟结点。在图6.1中,结点E、G、H互为堂兄弟。,前辈:层号比该结点小的结点,都称为该结点的前辈。,后辈:层号比该结点大的结点,都称为该结点的后辈。,4.树的抽象数据类型,数据对象D:一个集合,该集合中的所有元素具有相同的特性。,数据关系R:若D为空集,则为空树。若D中仅含有一个数据元素,则R为空集,否则R=H,H是如下的二元关系:,(1)在D中存在唯一的称为根的数据元素root,它在关系H下没有前驱。,(2)除root以外,D中每个结点在关系H下都有且仅有一个前驱。,基本操作:,InitTree(Tree):将Tree初始化为一棵空树。(2)DestoryTree(Tree):销毁树Tree。(3)CreateTree(Tree):创建树Tree。(4)TreeEmpty(Tree):若Tree为空,则返回TRUE,否则返回FALSE。(5)Root(Tree):返回树Tree的根。(6)Parent(Tree,x):树Tree存在,x是Tree中的某个结点。若x为非根结点,则返回它的双亲,否则返回“空”。(7)FirstChild(Tree,x):树Tree存在,x是Tree中的某个结点。若x为非叶子结点,则返回它的第一个孩子结点,否则返回“空”。(8)NextSibling(Tree,x):树Tree存在,x是Tree中的某个结点。若x不是其双亲的最后一个孩子结点,则返回x后面的下一个兄弟结点,否则返回“空”。,基本操作:,(9)InsertChild(Tree,p,Child):树Tree存在,p指向Tree中某个结点,非空树Child与Tree不相交。将Child插入Tree中,做p所指向结点的子树。(10)DeleteChild(Tree,p,i):树Tree存在,p指向Tree中某个结点,1id,d为p所指向结点的度。删除Tree中p所指向结点的第i棵子树。(11)TraverseTree(Tree,Visit():树Tree存在,Visit()是对结点进行访问的函数。按照某种次序对树Tree的每个结点调用Visit()函数访问一次且最多一次。若Visit()失败,则操作失败。,6.2 二叉树,6.2.1 二叉树的定义与基本操作,6.2.2 二叉树的性质,6.2.3 二叉树的存储结构,6.2.1 二叉树的定义与基本操作,定义:我们把满足以下两个条件的树型结构叫做二叉树(Binary Tree):(1)每个结点的度都不大于2;(2)每个结点的孩子结点次序不能任意颠倒。,下面给出二叉树的五种基本形态:,二叉树的基本操作:,Initiate(bt):将bt初始化为空二叉树。(2)Create(bt):创建一棵非空二叉树bt。(3)Destory(bt):销毁二叉树bt。(4)Empty(bt):若bt为空,则返回TRUE,否则返回FALSE。(5)Root(bt):求二叉树bt的根结点。若bt为空二叉树,则函数返回“空”。(6)Parent(bt,x):求双亲函数。求二叉树bt中结点x的双亲结点。若结点x是二叉树的根结点或二叉树bt中无结点x,则返回“空”。,基本操作:,(7)LeftChild(bt,x):求左孩子。若结点x为叶子结点或x不在bt中,则返回“空”。(8)RightChild(bt,x):求右孩子。若结点x为叶子结点或x不在bt中,则返回“空”。(9)Traverse(bt):遍历操作。按某个次序依次访问二叉树中每个结点一次且仅一次。(10)Clear(bt):清除操作。将二叉树bt置为空树。,6.2.2 二叉树的性质,性质1:在二叉树的第i层上至多有2i-1个结点(i1)。,当i=1时,整个二叉树只有一根结点,此时2i-1=20=1,结论成立。,证明:,假设i=k时结论成立,即第k层上结点总数最多为2k-1个。,现证明当i=k+1时,结论成立:,因为二叉树中每个结点的度最大为2,则第k+1层的结点总数最多为第k层上结点最大数的2倍,即22k-1=2(k+1)-1,故结论成立。,性质2:深度为k的二叉树至多有2k-1个结点(k1)。,证明:,因为深度为k的二叉树,其结点总数的最大值是将二叉树每层上结点的最大值相加,所以深度为k的二叉树的结点总数至多为:,故结论成立。,性质3:对任意一棵二叉树T,若终端结点数为n0,而其度数为2的结点数为n2,则n0=n2+1。,证明:设二叉树中结点总数为n,n1为二叉树中度为1的结点总数。因为二叉树中所有结点的度小于等于2,所以有n=n0+n1+n2设二叉树中分支数目为B,因为除根结点外,每个结点均对应一个进入它的分支,所以有:n=B+1。又因为二叉树中的分支都是由度为1和度为2的结点发出,所以分支数目为:B=n1+2n2整理上述两式可得到:n=B+1=n1+2n2+1将n=n0+n1+n2代入上式得出n0+n1+n2=n1+2n2+1,整理后得n0=n2+1,故结论成立。,两种特殊的二叉树:,满二叉树:深度为k且有2k-1个结点的二叉树。在满二叉树中,每层结点都是满的,即每层结点都具有最大结点数。,满二叉树,完全二叉树:,关系:满二叉树必为完全二叉树,而完全二叉树不一定是满二叉树。,深度为k,结点数为n的二叉树,如果其结点1n的位置序号分别与满二叉树的结点1n的位置序号一一对应,则为完全二叉树,性质4:具有n个结点的完全二叉树的深度为2n+1。,证明:设n个结点的完全二叉树的深度为k,根据性质2可知,k-1层满二叉树的结点总数为:2k-1-1 k层满二叉树的结点总数为:2k-1 显然有:2k-1-1 n 2k-1 2k-1 n 2k 取对数有:k-1 log2n k因为k是整数,所以k-1=log2n,k=2n+1结论成立。,性质5:对于具有n个结点的完全二叉树,如果按照从上到下和从左到右的顺序对二叉树中的所有结点从1开始顺序编号,则对于任意的序号为i的结点有:,(1)若i=1,则 i 无双亲结点 若i 1,则 i 的双亲结点为i/2(2)若2*i n,则 i 无左孩子 若2*in,则 i 结点的左孩子结点为2*i(3)若 2*i+1 n,则i 无右孩子 若 2*i+1n,则i的右孩子结点为2*i+1,用归纳法证明其中的(2)和(3)。,6.2.3 二叉树的存储结构,二叉树的结构是非线性的,每一结点最多可有两个后继。,二叉树的存储结构有两种:顺序存储结构和链式存储结构。,1.顺序存储结构:是用一组连续的存储单元来存放二叉树的数据元素。,二叉树的顺序存储结构,对于一般的二叉树,我们必须按照完全二叉树的形式来存储,就会造成空间的浪费。单支树就是一个极端情况。,单支树,2.链式存储结构,对于任意的二叉树来说,每个结点只有两个孩子,一个双亲结点。我们可以设计每个结点至少包括三个域:数据域、左孩子域和右孩子域:,二叉链表,二叉树T,二叉链表,typedef struct NodeDataType data;strct Node*LChild;struct Node*RChild;BiTNode,*BiTree;,用C语言描述定义二叉树的二叉链表结构如下:,结论:若一个二叉树含有n个结点,则它的二叉链表中必含有2n个指针域,其中必有n1个空的链域。,证明:,分支数目B=n-1,即非空的链域有n-1个,故空链域有2n-(n-1)=n+1个。,为了便于找到双亲结点,可以增加一个Parent域,以指向该结点的双亲结点。采用这种结点结构存放称做二叉树的三叉链表存储结构。,6.3 二叉树的遍历与线索化,6.3.1 二叉树的遍历6.3.2 遍历算法应用6.3.3 基于栈的递归消除6.3.4 线索二叉树6.3.5 由遍历序列确定二叉树,6.3 二叉树的遍历与线索化,二叉树的遍历:指按一定规律对二叉树中的每个结点进行访问且仅访问一次。,二叉树的基本结构由根结点、左子树和右子树组成,如图示,6.3.1 二叉树的遍历,用L、D、R分别表示遍历左子树、访问根结点、遍历右子树,那么对二叉树的遍历顺序就可以有:,访问根,遍历左子树,遍历右子树(记做DLR)。访问根,遍历右子树,遍历左子树(记做DRL)。遍历左子树,访问根,遍历右子树(记做LDR)。遍历左子树,遍历右子树,访问根(记做LRD)。遍历右子树,访问根,遍历左子树(记做RDL)。遍历右子树,遍历左子树,访问根(记做RLD)。,在以上六种遍历方式中,如果我们规定按先左后右的顺序,那么就只剩有 DLR、LDR 和LRD三种。根据对根的访问先后顺序不同,分别称DLR为先序遍历或先根遍历;LDR为中序遍历(对称遍历);LRD为后序遍历。,注意:先序、中序、后序遍历是递归定义的,即在其子树中亦按上述规律进行遍历。,三种遍历方法的递归定义:,(1)先序遍历(DLR)操作过程:,若二叉树为空,则空操作,否则依次执行如下操作:访问根结点;按先序遍历左子树;按先序遍历右子树。,若二叉树为空,则空操作,否则依次执行如下操作:按中序遍历左子树;访问根结点;按中序遍历右子树。,(2)中序遍历(LDR)操作过程,(3)后序遍历(LRD)操作过程:,若二叉树为空,则空操作,否则依次执行如下操作:按后序遍历左子树;按后序遍历右子树;访问根结点。,对于如下图的二叉树,其先序、中序、后序遍历的序列为:先序遍历:A、B、D、F、G、C、E、H。中序遍历:B、F、D、G、A、C、E、H。后序遍历:F、G、D、B、H、E、C、A。,以二叉链表作为存储结构,讨论二叉树的遍历算法,1)先序遍历,void PreOrder(BiTree root)/*先序遍历二叉树,root为指向二叉树(或某一子树)根结点的指针*/if(root!=NULL)Visit(root-data);/*访问根结点*/PreOrder(root-LChild);/*先序遍历左子树*/PreOrder(root-RChild);/*先序遍历右子树*/,2)中序遍历,void InOrder(BiTree root)/*中序遍历二叉树,root为指向二叉树(或某一子树)根结点的指针*/if(root!=NULL)InOrder(root-LChild);/*中序遍历左子树*/Visit(root-data);/*访问根结点*/InOrder(root-RChild);/*中序遍历右子树*/,3)后序遍历,void PostOrder(BiTree root)/*后序遍历二叉树,root为指向二叉树(或某一子树)根结点的指针*/if(root!=NULL)PostOrder(root-LChild);/*后序遍历左子树*/PostOrder(root-RChild);/*后序遍历右子树*Visit(root-data);/*访问根结点*/,以中序遍历为例来说明中序遍历二叉树的递归过程,A,B,D,C,E,第一次经过,第二次经过,第三次经过,6.3.2 遍历算法应用,1.输出二叉树种的结点,【算法描述】void PreOrder(BiTree root)/*先序遍历输出二叉树结点,root为指向二叉树根结点的指针*/if(root!=NULL)printf(root-data);/*输出根结点*/PreOrder(root-LChild);/*先序遍历左子树*/PreOrder(root-RChild);/*先序遍历右子树*/,【算法思想】输出二叉树中的结点并无次序要求,因此可用三种遍历算法中的任何一种完成,只需将访问操作具体变为打印操作即可。下面给出采用前序遍历实现的算法。,2.输出二叉树中的叶子结点,【算法思想】输出二叉树中的叶子结点与输出二叉树中的结点相比,它是一个有条件的输出问题,即在遍历过程中走到每一个结点时需进行测试,看是否满足叶子结点的条件。,【算法描述】void PreOrder(BiTree root)/*先序遍历输出二叉树中的叶子结点,root为指向二叉树根结点的指针*/if(root!=NULL)if(root-LChild=NULL/*先序遍历右子树*/,3.统计叶子结点数目,【算法思想】统计二叉树中的叶子结点数目并无次序要求,因此可用三种遍历算法中的任何一种完成,只需将访问操作具体变为判断是否为叶子结点及统计操作即可。,【算法描述】/*LeafCount为保存叶子结点数目的全局变量,调用之前初始化值为0*/void leaf(BiTree root)if(root!=NULL)leaf(root-LChild);leaf(root-RChild);if(root-LChild=NULL,方法一:,【算法思想】采用递归算法,如果是空树,返回0;如果只有一个结点,返回1;否则为左右子树的叶子结点数之和。,【算法描述】int leaf(BiTree root)int LeafCount;if(root=NULL)LeafCount=0;else if(root-LChild=NULL),方法二:,3.统计叶子结点数目,4.建立二叉链表方式存储的二叉树,【算法思想】采用类似先序遍历的递归算法,首先读入当前根结点的数据,如果是.则将当前树根置为空,否则申请一个新结点,存入当前根结点的数据,分别用当前根结点的左子域和右子域进行递归调用,创建左右子树。,【算法描述】void CreateBiTree(BiTree*bt)char ch;ch=getchar();if(ch=.)*bt=NULL;else*bt=(BiTree)malloc(sizeof(BiTNode);(*bt)-data=ch;CreateBiTree(,5.求二叉树的高度,设函数表示二叉树bt的高度,则递归定义如下:若bt为空,则高度为0 若bt非空,其高度应为其左右子树高度的最大值加1,左子树,右子树,bt,hl,hr,High=max(hl+hr)+1,【算法思想】二叉树bt的高度可以递归定义如下:l 若bt为空,则高度为0l若bt非空,其高度应为其左右子树高度的最大值加1,【算法描述】int PostTreeDepth(BiTree bt)/*后序遍历求二叉树bt高度的递归算法*/int hl,hr,max;if(bt!=NULL)hl=PostTreeDepth(bt-LChild);/*求左子树的深度*/hr=PostTreeDepth(bt-RChild);/*求右子树的深度*/max=hlhr?hl:hr;/*得到左、右子树深度较大者*/return(max+1);/*返回树的深度*/else return(0);/*如果是空树,则返回0*/,求二叉树的高度是也可用前序遍历的方式实现。,【算法思想】二叉树的高度(深度)为二叉树中结点层次的最大值。设根结点为第1层的结点,所有h层的结点的左右孩子结点在h+1层。故可以通过遍历计算二叉树中的每个结点的层次,其中最大值即为二叉树的高度。,【算法描述】void PreTreeDepth(BiTeee bt,int h)/*先序遍历求二叉树bt高度的递归算法,h为bt指向结点所在层次,初值为1*/*depth为当前求得的最大层次,为全局变量,调用前初值为0*/if(bt!=NULL)if(hdepth)depth=h;/*如果该结点层次值大于depth,更新depth的值*/PreTreeDepth(bt-Lchild,h+1);/*遍历左子树*/PreTreeDepth(bt-Rchild,h+1);/*遍历右子树*/,6.按树状打印的二叉树,假设以二叉链表存储的二叉树中,每个结点所含数据元素均为单字母,要求实现如下图的打印结果。,分析:这是二叉树的横向显示问题,横向显示应是竖向显示的900旋转,又由于二叉树的横向显示算法一定是中序遍历算法,所以把横向显示的二叉树算法改为RDL结构,实现算法为:,【算法思想】(1)二叉树的横向显示应是二叉树竖向显示的90。旋转。分析图6.15图示可知,这种树形打印格式要求先打印右子树,再打印根,最后打印左子树,按由上而下顺序看,其输出的结点序列为:CFEADB,这恰为逆中序顺序。解决二叉树的横向显示问题采用“逆中序”遍历框架,所以横向显示二叉树算法为先右子树、再根结点、再左子树的RDL结构。(2)在这种输出格式中,结点的左右位置与结点的层深有关,故算法中设置了一个表示当前根结点层深的参数,以控制输出结点的左右位置,每当递归进层时层深参数加1。这些操作应在访问根结点时实现。,【算法描述】void PrintTree(BiTree bt,int nLayer)/*按竖向树状打印的二叉树*/if(bt=NULL)return;PrintTree(bt-RChild,nLayer+1);PrintTree(bt-LChild,nLayer+1);,for(int i=0;idata);/*按逆中序输出结点,用层深决定的左右位置*/,思考:对二叉树实现左右子树交换,是否可采用先序、中序、后序中的任何一种算法实现,请简要说明原因。,6.3.3 基于栈的递归消除,1.中序遍历二叉树的非递归算法,首先应用递归进层的三件事与递归退层的三件事的原则,直接先给出中序遍历二叉树的非递归算法基本实现思路。,在大量复杂的情况下,递归的问题无法直接转换成循环,需要采用工作栈消除递归。工作栈提供一种控制结构,当递归算法进层时需要将信息保留;当递归算法出层时需要从栈区退出信息。,【算法思想】(1)针对左递归,写出递归进层的三件事(2)接着写出左递归返回时应执行的语句:访问根结点(3)接着针对右递归,写出递归进层的三件事(4)针对递归退层,写出递归退层的三件事(左、右递归公用),void inorder(BiTree root);int i=0;p=bt;L1:if(p!=NULL)/*遍历左子树*/top=top+2;if(topm);stop-1=p;/*本层参数进栈*/stop=L2;/*返回地址进栈*/p=p-LChild;/*给下层参数赋值*/goto L1;/*转向开始*/L2:Visit(p-data);/*访问根*/top=top+2;if(topm);stop-1=p;/*遍历右子树*/,【算法描述】,stop=L3;p=p-RChild;goto L1;L3:if(top!=0)addr=stop;p=stop-1;/*取出返回地址*/top=top-2;/*退出本层参数*/goto addr;,可以看到,直接按定义得到的上述算法结构并不好,为使程序合理组织,需去掉goto语句,用循环句代替if与goto,此时返回断点已无保留的必要,栈区只需保留本层参数。,整理后的算法框图如下图所示:,【算法思想】从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:,(1)从当前结点开始,进栈并走左子树,直到左子树为空。(2)退栈并访问。(3)走右子树。,【算法描述】/*sm 表示栈,top表示栈顶指针*/void inorder(BiTree root)/*中序遍历二叉树,root为二叉树的根结点*/top=0;p=root;dowhile(p!=NULL)if(topm)return;top=top+1;stop=p;p=p-LChild;/*遍历左子树*/if(top!=0)p=stop;top=top-1;Visit(p-data);/*访问根结点*/p=p-RChild;/*遍历右子树*/while(p!=NULL|top!=0),【算法思想】从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:,(1)如果当前结点存在,则进栈并走左子树。(2)否则退栈并访问,然后走右子树。,【算法描述】void InOrder(BiTree root)/*中序遍历二叉树的非递归算法*/InitStack(,递归算法的时间复杂度分析:对有n个结点二叉树,该算法每循环一次,p指向一个结点或空(无左孩子或无右孩子的结点的空链域),因此指向空的次数为n+1,n为结点个数,故循环次数为n+(n+1)=2n+1,从而算法的复杂度为O(n)。递归算法的空间复杂度分析:所需栈的空间最多等于二叉树深度K乘以每个结点所需空间数,记作O(K),。表面上看,递归算法好象并没有使用栈,实际上递归算法的执行,需要反复多次的自己调用自己。每调用一次,系统内部都有系统运行栈区在支持,这是隐含的栈,需要保留本层参数、临时变量与返回地址等等。随着函数递归调用,运行栈继续增长,直到函数执行完,才彻底释放占用的栈空间。因此递归算法比非递归算法占用的空间更多。,2.后序遍历二叉树的非递归算法,【算法思想】从根结点开始,只要当前结点存在,或者栈不空,则重复下面操作:,(1)从当前结点开始,反复入栈并左走,直到空的左子树;(2)如果栈顶结点的右子为空,或者顶结点的右子为刚访问过的结点,则退栈并访问;(3)否则,右走一步。,【算法描述】void PostOrder(BiTree root)BiTNode*p,*q;BiTree sStack_Size;int top=0;q=NULL;p=root;while(p!=NULL|top!=0)while(p!=NULL)top=+;if(top=Stack_Size)OverFlow();/*栈溢出处理*/stop=p;p=p-LChild;/*遍历左子树*/if(top0)p=stop;if(p-RChild=NULL)|(p-RChild=q)/*无右孩子,或右孩子已遍历过*/,visit(p-data);/*访问根结点*/q=p;/*保存到q,为下一次已处理结点前驱*/top-;p=NULL;elsep=p-RChild;,6.3.4 线索二叉树,1.基本概念,二叉树的遍历运算是将二叉树中结点按一定规律线性化的过程。当以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,而不能直接得到结点在遍历序列中的前驱和后继信息。要得到这些信息,第一种方法是将二叉树遍历一遍,在遍历过程中便可得到结点的前驱和后继,但这种动态访问浪费时间;第二种方法是充分利用二叉链表中的空链域,将遍历过程中结点的前驱、后继信息保存下来。,在有n个结点的二叉链表中共有2n个链域,但只有n-1个有用非空链域,其余n+1个链域是空的。我们可以利用剩下的n+1个空链域来存放遍历过程中结点的前驱和后继信息。现作如下规定:,若结点有左子树,则其LChild域指向其左孩子,否则LChild域指向其前驱结点;若结点有右子树,则其RChild域指向其右孩子,否则RChild域指向其后继结点。,为了区分孩子结点和前驱、后继结点,为结点结构增设两个标志域,如下图所示:,其中:,线索:在这种存储结构中,指向前驱和后继结点的指针叫做线索。,线索链表:以这种结构组成的二叉链表作为二叉树的存储结构,叫做线索链表。,线索化:对二叉树以某种次序进行遍历并且加上线索的过程叫做线索化。,线索二叉树:线索化了的二叉树称为线索二叉树。,2.二叉树的线索化,【算法思想】,(1)中序线索化采用中序递归遍历算法框架。(2)加线索操作就是访问结点操作。(3)加线索操作需要利用刚访问过结点与当前结点的关系,因此设置一个指针pre,始终记录刚访问过的结点,其操作如下:如果当前遍历结点root的左子域为空,则让左子域指向pre;如果前驱pre的右子域为空,则让右子域指向当前遍历结点root;为下次做准备,当前访问结点root作为下一个访问结点的前驱pre。,void Inthread(BiTree root)/*对root所指的二叉树进行中序线索化,其中pre始终指向刚访问过的结点,其初值为NULL*/if(root!=NULL)Inthread(root-LChild);/*线索化左子树*/if(root-LChild=NULL)root-Ltag=1;root-LChile=pre;/*置前驱线索*/if(pre!=NULL/*线索化右子树*/,【算法描述】,3.在线索二叉树中找前驱、后继结点,(1)找结点的中序前驱结点,【算法描述】BiTNode*InPre(BiTNode*p)/*在中序线索二叉树中查找p的中序前驱,并用pre指针返回结果*/if(p-Ltag=1)pre=p-LChild;/*直接利用线索*/else/*在p的左子树中查找“最右下端”结点*/for(q=p-LChild;q-Rtag=0;q=q-RChild);pre=q;return(pre);,下面是对同一棵二叉树的遍历方法不同得到的不同线索树。,NULL,(2)在中序线索树中找结点后继,对于结点p,若要找其后继结点,当p-Rtag=1时,p-RChild即为p的后继结点;当p-Rtag=0时,说明p有右子树,此时p的中序后继结点即为其右子树的“最左下端”的结点。其查找算法如下:,【算法描述】BiTNode*InNext(BiTNode*p)/*在中序线索二叉树中查找p的中序后继结点,并用Next指针返回结果*/if(p-Rtag=1)Next=p-RChild;/*直接利用线索*/else/*在p的右子树中查找“最左下端”结点*/for(q=p-RChild;q-Ltag=0;q=q-LChild);Next=q;return(Next),4.遍历中序线索树,(1)在中序线索树上求中序遍历的第一个结点,【算法描述】BiTNode*InFirst(BiTree Bt)BiTNode*p=Bt;If(!p)return(NULL);while(p-LTag=0)p=p-Lchild;return p;,(2)遍历中序二叉线索树,【算法描述】void TInOrder(BiTree Bt)BITNode*p;P=InFirst(Bt);While(p)Visit(p);p=InNext(p);,5.线索二叉树的插入、删除运算,二叉树加上线索之后,当插入或删除一结点时,可能会破坏原树的线索。所以在线索二叉树中插入或删除结点的难点在于:插入一个结点后,仍要保持正确的线索。,我们主要以中序线索二叉树为例,说明线索二叉树的插入和删除运算。,(1)插入结点运算,在中序线索二叉树中插入结点可分为两种情况:第一种:将新的结点插入到二叉树中,作某结点的左孩子;第二种:将新的结点插入到二叉树中,作某结点的右孩子。,我们仅讨论后一种情况。,InsNode(BiTNode*p,BiTNode*r)表示在线索二叉树中插入r所指向的结点,做p所指结点的右孩子。,若结点p的右孩子为空,则插入结点r的过程很简单。原来p的后继变为r的后继,结点p变为r的前驱,结点r成为p的右孩子。结点r的插入对p原来的后继结点没有任何的影响。,结点的右孩子为空时的插入过程为:,插入前,插入后,若p的右孩子不为空,则插入后,p的右孩子变为r的右孩子结点,p变为r的前驱结点,r变为p的右孩子结点。这时还需要修改原来p的右子树中“最左下端”结点的左指针域,使它由原来的指向结点p变为指向结点r。插入过程为:,插入前,插入后,void InsNode(BiTNode*p,BiTNode*r)if(p-Rtag=1)/*p无右孩子*/r-RChild=p-RChild;/*p的后继变为r的后继*/r-Rtag=1;p-RChild=r;/*r成为p的右孩子*/r-LChild=p;/*p变为r的前驱*/r-Ltag=1;else/*p有右孩子*/s=p-RChild;while(s-Ltag=0)s=s-LChild;/*查找p结点的右子树的“最左下端”结点*/r-RChild=p-RChild;/*p的右孩子变为r的右孩子*/r-Rtag=0;r-LChild=p;/*p变为r的前驱*/r-Ltag=1;p-RChild=r;/*r变为p的右孩子*/s-LChild=r;/*r变为p原来右子树的“最左下端”结点的前驱*/,【算法描述】,(2)删除结点运算,与插入操作一样,在线索二叉树中删除一个结点也会破坏原来的线索,所以需要在删除的过程中保持二叉树的线索化。,在中序线索二叉树中删除结点r的过程为:,6.3.5 由遍历序列确定二叉树,由定义,二叉树的前序遍历是先访问根结点D,其次遍历左子树L,最后遍历右子树R。即在结点的前序序列中,第一个结点必是根D;而另一方面,由于中序遍历是先遍历左子树L,然后访问根D,最后遍历右子树R,则根结点D将中序序列分割成两部分:在D之前是左子树结点的中序序列,在D之后是右子树结点的中序序列。反过来,根据左子树的中序序列中结点个数,又可将前序序列除根以外分成左子树的前序序列和右子树的前序序列两个部分。依次类推,便可递归得到整棵二叉树。,例:已知结点的前序序列和中序序列分别为:前序序列:18 14 7 3 11 22 35 27中序序列:3 7 11 14 18 22 27 35 则可按上述分解求得整棵二叉树。,6.4 树、森林和二叉树的关系,6.4.1 树的存储结构6.4.2 树、森林与二叉树的相互转换6.4.3 树与森林遍历,6.4.1 树的存储结构,树的主要存储方法有:1.双亲表示法2.孩子表示法3.孩子兄弟表示法,1.双亲表示法:用一组连续的空间来存储树中的结点,在保存每个结点的同时附设一个指示器来指示其双亲结点在表中的位置,其结点的结构如下:,树的双亲表示法如下图:,双亲表示法的优点:利用了树中每个结点(根结点除外)只有一个双亲结点的性质,使得查找某个结点的双亲结点非常容易。,双亲表示法的缺点:在求某个结点的孩子时,需要遍历整个向量。,双亲表示法的存储结构,#define MAX 100typedef struct TNode DataType data;int parent;TNode;,一棵树可以定义为:,typedef struct TNode treeMAX;int nodenum;/*结点数*/ParentTree;,2.孩子表示法:通常是把每个结点的孩子结点排列起来,构成一个单链表,称为孩子链表。n个结点共有n个孩子链表(叶结点的孩子链表为空表),而n个结点的数据和n个孩子链表的头指针又组成一个顺序表。,r,孩子表示法的存储结构:,typedef struct ChildNode/*孩子链表结点的定义*/int Child;/*该孩子结点在线性表中的位置*/struct ChildNode*next;/*指向下一个孩子结点的指针*/ChildNode;typedef struct/*顺序表结点的结构定义*/DataType data;/*结点的信息*/ChildNode*FirstChild;/*指向孩子链表的头指针*/DataNode;typedef struct/*树的定义*/DataNode nodesMAX;/*顺序表*/int root,num;/*该树的根结点在线性表中的位置和该树的结点个数*/ChildTree;,3.孩子兄弟表示法(二叉链表表示法):链表中每个结点设有两个链域,分别指向该结点的第一个孩子结点和下一个兄弟(右兄弟)结点。,孩子兄弟表示法的存储结构:,typedef struct CSNode DataType data;/*结点信息*/Struct CSNode*FirstChild,*Nextsibling;/*第一个孩子,下一个兄弟*/CSNode,*CSTree;,优点:便于实现树的各种操作。,6.4.2 树、森林与二叉树的相互转换,1.树转换为二叉树,我们约定树中每一个结点的孩子结点按从左到右的次序顺序编号,也就是说,把树作为有序树看待。,例如:右图的树,将一棵树转换为二叉树的方法:,树中所有相邻兄弟之间加一条连线。对树中的每个结点,只保留其与第一个孩子结点之间的连线,删去其与其它孩子结点之间的连线。以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。,结论:从转换过程可看出:树中的任意一个结点都对应于二叉树中的一个结点。树中某结点的第一个孩子在二叉树中是相应结点的左孩子,树中某结点的右兄弟结点在二叉树中是相应结点的右孩子。也就是说,在二叉树中,左分支上的各结点在原来的树中是父子关系,而右分支上的各结点在原来的树中是兄弟关系。由于树的根结点没有兄弟,所以变换后的二叉树的根结点的右孩子必然为空。,树与二叉树的对应关系及转换方法,2.森林转换为二叉树,森林转换为二叉树的方法为:,(1)将森林中的每棵树转换成相应的二叉树。(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连在一起后,所得到的二叉树就是由森林转换得到的二叉树。,森林转换为二叉树的过程,将森林F看作树的有序集F=T1,T2,,TN,它对应的二叉树为B(F):则有:(1)若N=0,则B(F)为空。(2)若N0,二叉树B(F)的根为森林中第一棵树T1的根;B(F)的左子树为B(T11,T1m),其中T11,T1m是T1的子树森林;B(F)的右子树是B(T2,TN)。,用递归的方法描述上述转换过程:,3.二叉树还原为树或森林,一棵二叉树还原为树或森林,具体方法为:,(1)若某结点是其双亲的左孩子,则把该结点的右孩子、右孩子的右孩子、都与该结点的双亲结点用线连起来。(2)删掉原二叉树中所有双亲结点与右孩子结点的连线。(3)整理由(1)、(2)两步所得到的树或森林,使之结构层次分明。,用递归的方法描述其转换过程为:,若B是一