数据结构实例精讲二叉树及其存储.docx
- 文档编号:25548127
- 上传时间:2023-06-09
- 格式:DOCX
- 页数:53
- 大小:92.43KB
数据结构实例精讲二叉树及其存储.docx
《数据结构实例精讲二叉树及其存储.docx》由会员分享,可在线阅读,更多相关《数据结构实例精讲二叉树及其存储.docx(53页珍藏版)》请在冰豆网上搜索。
数据结构实例精讲二叉树及其存储
数据结构实例精讲:
二叉树及其存储
树形结构是一类非常重要的非线性结构,它用于描述数据元素之间的层次关系,如人类社会的族谱和各种社会组织机构的表示等等。
在计算机领域中,树形结构的应用也非常广泛,磁盘文件的目录结构就是一个典型的例子。
树和二叉树是常用的树形结构,由于二叉树表示对于树的存储和运算有很大意义,可以把对于树的许多处理转换到对应的二叉树中去做,因此,本章重点讨论二叉树的有关概念、存储结构和常用操作。
5.1树和二叉树的基本概念
5.1.1树的基本概念
树是一个或多个结点组成的有限集合T,有一个特定的结点称为树的根结点,其余的结点被分成m(m≥0)个不相交的集合T1、T2、…、Tm。
,每一个集合本身又是一棵树,被称为这个根结点的子树。
图5-1所示是一棵具有10个结点的树,结点A为树的根结点,除A之外的其余结点分为3个不相交的集合T1={B,E,F}、T2={C,G}和T3={D,H,I,J},形成了结点A的3棵子树,T1、T2和T3本身也分别是一棵树。
例如,子树T1的根结点为B,其余结点又分为两个不相交的集合{E}和{F},它们形成了子树T1的根结点B的两棵子树。
图5-1一棵具有10个结点的树
树的基本术语有:
孩子、双亲、兄弟:
树中某结点的各子树的根称做该结点的孩子;相应地该结点称做其孩子的双亲;具有相同双亲的结点互称为兄弟。
图5-1中,结点B、C、D都是结点A的孩子,结点A是结点B的双亲,也是结点C、D的双亲,结点B、C、D互为兄弟。
结点的度:
一个结点所拥有的子树的个数称为该结点的度。
图5-1中,结点A的度为3,结点B的度为2,结点E的度为0。
叶结点、分支结点:
度为0的结点称为叶结点,或者称为终端结点;度不为0的结点称为分支结点,或者称为非终端结点。
图5-1中,结点A、B、C、D为分支结点,结点E、F、G、H、I、J为叶结点。
结点的层数:
规定树的根结点的层数为1,其他任何结点的层数等于它的双亲结点的层数加1。
图5-1中,结点A的层数为1,结点B、C、D的层数为2,结点E、F、G、H、I、J的层数为3。
树的深度:
一棵树的叶结点的最大层数称为树的深度。
图5-1所示的树的深度为3。
5.1.2二叉树的基本概念
二叉树是结点的有限集合,这个有限集合或者为空集(称为空二叉树),或者由一个根结点及两棵不相交的、分别称做这个根的左子树和右子树的二叉树组成。
图5-2所示的是一棵二叉树,根结点为A,其左子树包含结点B、D、G、H,右子树包含结点C、E、F、I、J。
根A的左子树又是一棵二叉树,其根为结点B,有非空的左子树(由结点D、G、H组成)和空的右子树。
根A的右子树也是一棵二叉树,其根为结点C,有非空的左子树(由结点E、I、J组成)和右子树(由结点F组成)。
图5-2二叉树
上面介绍的树的基本术语在二叉树中同样适用,但需要说明的是,尽管树和二叉树的概念之间有许多关系,但它们是两个概念。
二叉树不是树的特殊情况,树和二叉树之间最主要的区别是:
二叉树是有序的,二叉树的结点的子树要区分左子树和右子树,即使在结点只有一棵子树的情况下也要明确指出该子树是左子树还是右子树。
例如,图5-3中是四棵不同的二叉树,但如果作为树,它们就是相同的了。
图5-3四棵不同的二叉树
满二叉树和完全二叉树是两种特殊形态的二叉树。
满二叉树:
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶结点都在同一层上,这样的一棵二叉树称做满二叉树。
完全二叉树:
一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
显然,一棵满二叉树必定是一棵完全二叉树。
在图5-4中,(a)为一棵满二叉树,(b)为一棵完全二叉树,(c)为一棵非完全二叉树。
图5-4满二叉树、完全二叉树和非完全二叉树
5.1.3二叉树的性质
二叉树具有下列重要性质:
【性质1】在二叉树中,第i层的结点数最多为2i-1个(i≥1)。
利用归纳法来证明。
i=1时,只有一个根结点,2i–1=20=1成立。
假设对所有的i=k(k≥1)命题成立,即第k层上至多有2k-1个结点。
则当i=k+1时,由于二叉树的每个结点的度数至多为2,所以在第k+1层上的最大结点数为第k层上的最大结点数的2倍,即2*2k-1=2k,命题成立。
【性质2】在深度为k的二叉树中,结点总数最多为2k-1个(k≥1)。
由性质1知,深度为k的二叉树的最大结点数为
(5-1)
可见,所谓满二叉树就是深度为k且有2k-1个结点的二叉树,即一棵满二叉树中,每一层的结点数均达到了最大值。
【性质3】对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
设二叉树T中度为1的结点数为n1,因为二叉树中结点的度数均小于或等于2,所以其结点总数为
n=n0+n1+n2(5-2)
又设B为二叉树T中总的分支数目。
因为除根结点外,其余结点都有一个分支进入,所以B=n-1。
但这些分支是由度数为1或2的结点射出的,所以又有B=n1+2n2,于是得
n=n1+2n2+1(5-3)
由式(5-2)和式(5-3)得n0=n2+1
【性质4】具有n个结点的完全二叉树的深度为[log2n]+1。
证明:
假设深度为k,则根据性质2和完全二叉树的定义有
2k-1-1 于是k-1≤log2n 因为k是整数,所以k=[log2n]+1 【性质5】如果对一棵有n个结点的完全二叉树的结点从1到n按层序编号,则对任一结点i(1≤i≤n),有: 1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]。 2)如果2i>n,则结点i无左孩子(结点i为叶子结点);否则,其左孩子是结点2i。 3)如果2i+1>n,则结点i无右孩子;否则,其右孩子是结点2i+1。 5.2二叉树的存储 1.二叉树的顺序存储结构 所谓顺序存储结构,就是用一组连续的存储单元存放二叉树中的结点。 完全二叉树由于其结构上的特点,通常采用顺序方式存储。 按从上至下、从左到右的次序将一棵有n个结点的完全二叉树的所有结点从1到n编号,就得到结点的一个线性序列,如同图5-4(b)所示。 由二叉树的“性质5”知道,在完全二叉树中从一个结点的编号就可以推知它的双亲及左、右孩子结点的编号,即完全二叉树中结点的序号可以唯一地反映出结点之间的逻辑关系,所以可以用一维数组按从上至下、从左到右的顺序存储树中所有结点的数据信息,通过数组元素的下标关系反映完全二叉树中结点之间的逻辑关系。 图5-5给出了图5-4(b)所示的完全二叉树的顺序存储示意图(注意由于数组下标从0开始,因此数组元素的下标等于结点在完全二叉树中的序号减1)。 图5-5完全二叉树的顺序存储示意图 对于一般的二叉树,如果仍按照从上至下、从左到右的顺序将树中的结点顺序存储在一维数组中,则数组元素下标之间的关系不能反映二叉树中结点之间的逻辑关系,这时我们假设可将一般二叉树进行改造,增添一些并不存在的空结点,使之成为一棵完全二叉树的形式,然后再用一维数组顺序存储。 在二叉树中假设增添的结点在数组中所对应的元素值为“空”。 图5-6给出了图5-4(c)所示的非完全二叉树的顺序存储示意图。 图5-6非完全二叉树的顺序存储示意图 2.二叉树的链式存储结构 二叉树的链式存储结构是用链建立二叉树中结点之间的关系,通常采用的链式存储结构为二叉链表。 所谓二叉链表,是将二叉树中的结点设置为如下的结构体类型: lchild data rchild 其中data表示保存结点本身信息的信息域,lchild和rchild分别为指向结点的左孩子和右孩子的指针域。 由于每个结点有两个指针域,所以形象地称之为二叉链表。 当二叉树采用二叉链表存储结构时,如果某结点的左孩子或右孩子不存在,则相应的指针域为空。 此外,设置一指针变量指向二叉树的根结点,称之为头指针。 和单链表中头指针的作用相似,二叉链表中的头指针可以唯一地确定一棵二叉树。 图5-7给出了图5-4(c)所示的非完全二叉树的二叉链表存储示意图。 图5-7二叉链表存储示意图 【实例5-1】创建二叉树。 编写程序,根据输入的结点信息建立二叉树的二叉链表存储结构。 (1)问题分析。 定义二叉树的二叉链表存储方式的结点结构为: structBiTNode { TElemTypedata; BiTNode*lchild; BiTNode*rchild; }; 定义二叉树类型为: typedefBiTNode*BiTree; 在建立二叉树的二叉链表存储结构时,可以通过输入各个非终端结点的情况来完成二叉树的创建。 设有二叉树如图5-8所示,该树有9个结点,先动态分配一个指向二叉链表结点类型的指针数组pNode[9],依次输入9个结点的元素值ch,为每个结点i(0<=i<9)分配存储空间,用指针pNode[i]指向结点i所分配的存储空间,使该结点的数据域等于ch,两个指针域均为空;然后依次输入结点间的关系,输入时按“父结点序号、左子结点序号和右子结点序号方式”定义。 若某结点不存在左子或右子结点,则子结点序号输入为0。 定义关系时,每个非终端结点定义一次,叶子结点无需定义。 例如输入“123”,则表示结点A的左子结点为B,右子结点为C,由这一输入可以修改结点A的左子和右子指针域,从而将它们链接起来。 图5-8一棵二叉树 (2)源程序。 #include usingnamespacestd; typedefcharTElemType; structBiTNode { TElemTypedata; BiTNode*lchild; BiTNode*rchild; }; typedefBiTNode*BiTree; BiTNode*CreateBiTNode(TElemTypevalue) { BiTNode*pNode=newBiTNode(); pNode->data=value; pNode->lchild=NULL; pNode->rchild=NULL; returnpNode; } voidConnectTreeNodes(BiTNode*pParent,BiTNode*pLeft,BiTNode*pRight) { if(pParent! =NULL) { pParent->lchild=pLeft; pParent->rchild=pRight; } } voidPrintTreeNode(BiTNode*pNode) { if(pNode! =NULL) { cout<<"结点"< if(pNode->lchild! =NULL) cout<<"左子为"< else cout<<"左子为空"; if(pNode->rchild! =NULL) cout<<",右子为"< "< else cout<<",右子为空。 "< } } voidPrintTree(BiTreebt) { PrintTreeNode(bt); if(bt! =NULL) { if(bt->lchild! =NULL) PrintTree(bt->lchild); if(bt->rchild! =NULL) PrintTree(bt->rchild); } } voidDestroyTree(BiTree&bt) { if(bt! =NULL) { BiTNode*pLeft=bt->lchild; BiTNode*pRight=bt->rchild; deletebt; bt=NULL; DestroyTree(pLeft); DestroyTree(pRight); } } intmain() { intn,num,i,j,k; charch; BiTNode**pNode; cout<<"请输入二叉树中的结点个数: "; cin>>n; pNode=newBiTNode*[n+1]; pNode[0]=NULL; cout<<"请依次输入各结点的数据元素值(第1个结点输入一定为根结点): "; for(i=1;i<=n;i++) { cin>>ch; pNode[i]=CreateBiTNode(ch); } cout<<"树中结点元素值与结点序号的对应关系如下: "< for(i=1;i<=n;i++) cout< cout< cout<<"请输入结点间的关系,输入时按父结点序号、左子结点序号和右子结点序号方式定义。 "< cout<<"若某结点不存在左子或右子结点,则子结点序号输入为0。 定义关系时,每个非终端结点定义一次,叶子结点无需定义。 "; cout<<"例如输入123,则表示结点"< cout<<"请输入树中非终端结点数目"; cin>>num; cout<<"请按非终端结点序号其左子结点序号其右子结点序号的方式输入各非终端结点的子结点情况。 "< for(n=0;n { cin>>i>>j>>k; ConnectTreeNodes(pNode[i],pNode[j],pNode[k]); } if(pNode[1]! =NULL) PrintTree(pNode[1]); else cout<<"ThebinaryTreeisnull."< DestroyTree(pNode[1]); delete[]pNode; return0; } 由于在后面的有关二叉树的遍历的实例中,需要先创建一棵由二叉链表保存的二叉树,因此可以编写如下一个函数,创建一棵用于测试的试验树,该二叉树如图5-8所示。 voidCreateTestBinTree(BiTree&bt) { BiTNode*pNode1=CreateBiTNode('A'); BiTNode*pNode2=CreateBiTNode('B'); BiTNode*pNode3=CreateBiTNode('C'); BiTNode*pNode4=CreateBiTNode('D'); BiTNode*pNode5=CreateBiTNode('E'); BiTNode*pNode6=CreateBiTNode('F'); BiTNode*pNode7=CreateBiTNode('G'); BiTNode*pNode8=CreateBiTNode('H'); BiTNode*pNode9=CreateBiTNode('I'); ConnectTreeNodes(pNode1,pNode2,pNode3); ConnectTreeNodes(pNode2,pNode4,NULL); ConnectTreeNodes(pNode4,NULL,pNode7); ConnectTreeNodes(pNode3,pNode5,pNode6); ConnectTreeNodes(pNode5,pNode8,pNode9); bt=pNode1; } 【实例5-2】广义表和二叉树。 树可以采用广义表来表示,其具体规定如下: ①树的根结点作为由子树构成的表的表名放在表的最前面。 ②每个结点的左子树和右子树用逗号隔开。 若仅有右子树没有左子树,逗号不能省略。 ③在整个广义表输入的结尾加上一个特殊的符号(例如“#”,一般采用字符串结束符“\0”)表示输入结束。 例如,对于如图5-8所示的二叉树,其广义表表示为A(B(D(,G)),C(E(H,I),F))。 编写一个函数,根据存储在字符串Ls中的广义表表示,创建其对应的二叉树的二叉链表存储结构。 (1)问题分析。 依次从保存广义表表示的字符串Ls中读取每个字符。 若遇到的是字母(假设以字母作为结点的值),则表示是结点的值,为它建立一个新的结点,并把该结点作为左子女(当k=1)或右子女(当k=2)链接到其双亲结点上。 若遇到的是左括号“(”,则表明是子表的开始,将k置为1;若遇到的是右括号“)”,则表明子表结束。 若遇到的是逗号“,”,则表示以左子女为根的子树处理完毕,接着处理以右子女为根的子树,将k置为2。 为保存某结点的双亲信息,在处理中需要使用了一个栈s,在进入子表之前,将根结点指针进栈,以便括号内的子女链接之用。 在子表处理结束时退栈。 相关的栈操作可参见第3章的有关内容。 (2)函数程序代码。 voidCreatBinTreeFromGList(BiTree&bt,charLs[]) { SqStacks; InitStack(s);//栈初始化为空 bt=NULL;//置空二叉树 BiTNode*p=NULL,*q; inti=0,k; while(Ls[i]! ='\0')//逐个字符处理,直到遇到'\0'为止 { switch(Ls[i]) { case'(': Push(s,p);k=1;break;//根结点入栈,同时记k=1 case')': Pop(s);break;//当前子树处理完,其根结点出栈 case',': k=2;break;//记k=2,其后处理的是右子树 default: p=newBiTNode;p->data=Ls[i]; p->lchild=NULL;p->rchild=NULL; if(bt==NULL)bt=p; elseif(k==1){q=GetTop(s);q->lchild=p;} else{q=GetTop(s);q->rchild=p;} } i++; } } (3)函数应用的示例源程序。 #include usingnamespacestd; typedefcharTElemType; structBiTNode { TElemTypedata; BiTNode*lchild; BiTNode*rchild; }; typedefBiTNode*BiTree; #defineMAXNUM100//假定预分配的栈空间最多为100个元素 typedefBiTNode*ElemType;//栈元素的数据类型为指向二叉树结点的指针 typedefstruct{ ElemTypedata[MAXNUM]; inttop; }SqStack; voidInitStack(SqStack&S)//置栈空 { S.top=-1; } boolStackEmpty(SqStackS)//判栈空 { returnS.top==-1; } voidPush(SqStack&S,ElemTypex)//入栈 { if(S.top==MAXNUM-1) { cout<<"栈上溢"< return; } S.data[++S.top]=x;//栈顶指针加1后将x入栈 } voidPop(SqStack&S)//出栈,不返回栈顶元素 { if(StackEmpty(S)) { cout<<"栈为空"< return; } S.top--;//将栈顶指针减1 } ElemTypeGetTop(SqStackS)//取栈顶元素 { if(StackEmpty(S)) { cout<<"栈为空"< exit (1); } returnS.data[S.top]; } voidPrintTreeNode(BiTNode*pNode) { if(pNode! =NULL) { cout<<"结点"< if(pNode->lchild! =NULL) cout<<"左子为"< else cout<<"左子为空"; if(pNode->rchild! =NULL) cout<<",右子为"< "< else cout<<",右子为空。 "< } } voidPrintTree(BiTreebt) { PrintTreeNode(bt); if(bt! =NULL) { if(bt->lchild! =NULL) PrintTree(bt->lchild); if(bt->rchild! =NULL) PrintTree(bt->rchild); } } voidDestroyTree(BiTree&bt) { if(bt! =NULL) { BiTNode*pLeft=bt->lchild; BiTNode*pRight=bt->rchild; deletebt; bt=NULL; DestroyTree(pLeft); DestroyTree(pRight); } } voidCreatBinTreeFromGList(BiTree&bt,charLs[]) { SqStacks; InitStack(s);//栈初始化为空 bt=NULL;//置空二叉树 BiTNode*p=NULL,*q; inti=0,k; while(Ls[i]! ='\0')//逐个字符处理,直到遇到'\0'为止 { switch(Ls[i]) { case'(': Push(s,p);k=1;break;//根结点入栈,同时记k=1 ca
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构 实例 二叉 及其 存储
![提示](https://static.bdocx.com/images/bang_tan.gif)