理学第6章 树.docx

上传人:b****6 文档编号:12832525 上传时间:2023-06-08 格式:DOCX 页数:78 大小:455.47KB
下载 相关 举报
理学第6章 树.docx_第1页
第1页 / 共78页
理学第6章 树.docx_第2页
第2页 / 共78页
理学第6章 树.docx_第3页
第3页 / 共78页
理学第6章 树.docx_第4页
第4页 / 共78页
理学第6章 树.docx_第5页
第5页 / 共78页
理学第6章 树.docx_第6页
第6页 / 共78页
理学第6章 树.docx_第7页
第7页 / 共78页
理学第6章 树.docx_第8页
第8页 / 共78页
理学第6章 树.docx_第9页
第9页 / 共78页
理学第6章 树.docx_第10页
第10页 / 共78页
理学第6章 树.docx_第11页
第11页 / 共78页
理学第6章 树.docx_第12页
第12页 / 共78页
理学第6章 树.docx_第13页
第13页 / 共78页
理学第6章 树.docx_第14页
第14页 / 共78页
理学第6章 树.docx_第15页
第15页 / 共78页
理学第6章 树.docx_第16页
第16页 / 共78页
理学第6章 树.docx_第17页
第17页 / 共78页
理学第6章 树.docx_第18页
第18页 / 共78页
理学第6章 树.docx_第19页
第19页 / 共78页
理学第6章 树.docx_第20页
第20页 / 共78页
亲,该文档总共78页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

理学第6章 树.docx

《理学第6章 树.docx》由会员分享,可在线阅读,更多相关《理学第6章 树.docx(78页珍藏版)》请在冰点文库上搜索。

理学第6章 树.docx

理学第6章树

第六章树

树形结构是一种日常生活中应用非常广泛的非线性结构。

从单位的组织结构、家谱到计算机领域中的操作系统、数据库系统都是树状结构的应用实例,甚至某些数据库管理系统就是利用树状结构的原理设计的。

6.1树简介

什么是树结构?

先看一下它的定义:

树(Tree)是一种特殊的数据结构,它可以用来描述有分支的结构,是由一个或一个以上的结点所组成的有限集合,具有下列性质:

●存在一个特殊的结点,称为根(Root)。

●其余结点为n≥0个互斥的集合,即T1,T2,T3,…,Tn,且每个集合称为子树。

如下图所示,A为根结点,B、C、D、E均为A的子结点。

比较下面的两个图形

图(a)是一种合法的树,符合树的定义:

树由一个或一个以上的结点组成,结点间有连接且不形成回路。

图(b)中结点有连接但却形成了回路,故不是一棵合法的树。

接下来了解一下树的专有名词,以下图为例进行说明。

●树根或根结点(Root):

没有父结点的结点称为根结点,如A。

●父结点(Parend):

每一个结点的上层结点称为其父结点,如B的父结点为A,G的父结点为C。

●子结点(Children):

每一个结点的下层结点称为其子结点,如B的子结点为E及F,A的子结点为B、C、D。

●兄弟结点(Siblings):

有共同父结点的结点称为兄弟结点,如B、C、D的父结点都是A,所以彼此为兄弟结点。

●结点的度(Degree):

子结点的个数为该结点的度,如A的度为3,B的度为2。

●叶子结点(TerminalNode):

没有子结点的结点,即度为零的结点,例如E、F、G、H、D都是叶子结点。

●非叶子结点(Non-TerminalNode):

叶子结点以外的所有结点为非叶子结点,即度不为零的点,如A、B、C都是非叶子结点。

●边(Edge):

连接两个结点的线段,称之为边。

上图中的边共有7条,一般地,具有n个结点的树,其边数为n-1。

●层(Level):

树的层,即树根A为层1,B、C、D为层2,E、F、G、H为层3。

●高度(Height):

树的最大层数。

●森林(Forest):

森林是由n棵互斥的树组成的集合(n≥0)。

【范例1】下列哪一种结构不是树(Tree)。

A.一个结点B.环形序列

C.一个没有回路的连通图D.一个边数比结点数少1的连通图

解答:

选择B,因为环形序列会形成循环回路现象,不符合树的定义。

【范例2】下图中的树(Tree)中有几个叶子结点?

A.4B.5C.9D.11

解答:

由上图可以看出答案为A,共有E、C、H、I共4个叶子结点。

6.2二叉树简介

一般的树状结构在内存中的存储方式以链表为主,对于n叉树来说,每个结点的度不一定相同,但为了方便起见,必须取n+1为链表结点的最长固定长度,其数据结构如下:

data

link1

link2

linkn

假设n叉树有m个结点,那么此树共需要(n+1)×m个存储单元。

而每个结点不一定都会指向下一层的n个结点,会有大量的空值,如此一来,会造成大量的内存空间浪费,为了克服这一缺点,常使用二叉树结构来取代树状结构。

6.2.1二叉树的定义

二叉树是由有限个结点组成的集合,此集合可以为空集合,也可由一个树根或者一个树根与左右两棵子树所组成。

简单的说,二叉树最多只能有两个子结点,即结点的度小于等于2,其数据结构如下:

left

data

right

二叉树和一般树的不同点如下:

●树不可为空集合,但是二叉树可以。

●树的度d≥0,但二叉树的结点度0≤d≤2。

●树的子树间没有次序关系,但二叉树有次序关系。

6.2.2特殊二叉树的介绍

1.满二叉树

如果二叉树高度为n,树的结点为2n-1,则称此树为满二叉树,如下图所示。

2.完全二叉树

如果二叉树的高度为n,则1~n-1层都是满的,第n层的叶子结点严格按从左至右依次排列,则称此二叉树为完全二叉树,如下图所示。

【性质1】假设一棵完全二叉树具有

个节点,则它的高度(层)

证明:

设完全二叉树的高度为

,则有

3.严格二叉树

如果二叉树的每个非叶子结点都有非空的左、右子树,则称此二叉树为严格二叉树,如下图所示。

4.歪斜树

当一个二叉树完全没有右结点或左结点时,称为左歪斜树或右歪斜树,如下图所示。

5.排序二叉树

设结点由关键字值来表示,用所有结点的关键字值互异,排序二叉树或者是一棵空树,或者是满足以下条件的树:

(1)若左子树不空,则左子树上所有结点的关键字值均小于根结点的关键字值;

(2)若右子树不空,则右子树上所有结点的关键字值均大于根结点的关键字值;

(3)左、右子树也分别是二叉排序树。

创建这种二叉树是最为方便的,它的中序遍历是关键字值的升序排序,故而称之为二叉排序树。

【性质2】高度为h的二叉树的结点总数n≤2h-1。

证明:

其结点总数为各层中度最大的结点数之和,即

【性质3】对于非空二叉树T,如果n0为叶子(即度为0的结点)结点数,且度为2的结点数是n2,试证明:

n0=n2+1。

证明:

假设结点总数为n,n1是度为1的结点数,边数为e,于是,我们有以下关系式

n=n0+n1+n2,e=n-1,e=2n2+n1

由上述三个式子,有n0=n2+1。

6.3二叉树的存储方式

树状结构在程序中的建立大多使用链表来处理,因为链表的指针用来处理树是相当方便的,只需改变指针即可。

当然也可以使用数组这样的连续结构来表示二叉树,使用数组或者链表各有利弊,下面将给予说明。

6.3.1二叉树的数组表示法

如果用一维数组来存储二叉树,首先应将二叉树想象成一棵完全二叉树,凡空值结点均用特殊字符或者数字替代,并且依序将结点值存放到一维数组中。

【范例3】将下图所示的二叉树,用一维字符数组进行表示。

首先将二叉树想象成一棵完全二叉树,其空值结点用*代替,结束符用!

表示。

其一维字符数组的表示形式为:

索引值

1

2

3

4

5

6

7

内容值

A

B

C

*

*

D

【范例程序】用一维字符数组表示结点值为字符的二叉树。

/*

名称:

ch06_01.cpp

示范:

用一维字符数组表示结点值为字符的二叉树

*/

#include

#include

usingnamespacestd;

#defineMaxSize100//数组的最大维数

#defineMaxLevel6//设定可输出的二叉树最大层数

intNodeNum=0;//结点计数变量

charNode[MaxSize];//字符型的结点数组

//创建二叉树的结点数组

voidCreateTreeArray()

{cout<<"按完全二叉树方式输入,若某层有元素为空,用*替代,结束用!

替代"<

Node[0]=0;//使Node[0]闲置

while

(1)

{NodeNum++;

cin>>Node[NodeNum];//输入结点值

if(Node[NodeNum]=='!

')

break;

if(NodeNum==MaxSize)

{cout<<"输入字符个数超限,无法存储,请增大MaxSize的值"<

exit(0);

}

}

}

//输出树

voidDisplayTree()

{inti,j,k;//循环控制变量

inthight,level,SerialNum;//hight树高,level树层,SerialNum序号

//确定树的高度

for(i=0;i

{if(pow(2,i)>NodeNum)

break;

}

hight=i;

//从第1层至第hight层进行二叉树的输出

level=1;SerialNum=1;

while(level<=hight)

{//每层前端空格输出控制

for(i=0;i

cout<<"";

//每层输出的结点个数控制

for(j=0;j

{

if(Node[SerialNum]!

='*'&&Node[SerialNum]!

='!

')

cout<

elseif(Node[SerialNum]=='*')

cout<<"";//输出空格

else

break;//遇到结束符!

跳出输出

for(k=0;k

cout<<"";//每层后端空格输出控制

}

cout<

level++;//层数增1

}

cout<

}

//输出数组

voidDisplayArray()

{inti=1;

do

{cout<

i++;

}while(Node[i]!

='!

');

cout<

}

//主函数

voidmain()

{CreateTreeArray();//创建二叉树结点的一维数组

cout<<"二叉树为"<

DisplayTree();//输出树

cout<<"结点数组为"<

DisplayArray();//输出数组

system("pause");

}

如果二叉树的结点值是由整数给出的,对它的处理也是类似的。

【范例4】将下图所示的二叉树,用一维整型数组进行表示。

首先将二叉树想象成一棵完全二叉树,其空值结点用0代替,结束符用-1表示。

其一维整型数组的表示形式为:

索引值

1

2

3

4

5

6

7

8

9

10

11

12

13

14

内容值

6

3

9

2

5

7

0

0

0

4

0

0

8

-1

仅需要对上述示范程序作几处简单的修改,就可以用于解决上述问题的编程。

【范例程序】用一维整型数组表示结点值为整数的二叉树。

/*

名称:

ch06_02.cpp

示范:

用一维整型数组表示结点值为整数的二叉树

*/

#include

#include

usingnamespacestd;

#defineMaxSize100//数组的最大维数

#defineMaxLevel6//设定可输出的二叉树最大层数

intNodeNum=0;//结点计数变量

intNode[MaxSize];//整型结点数组

//创建二叉树的结点数组

voidCreateTreeArray()

{

cout<<"按完全二叉树方式输入,若某层有元素为空,用0替代,结束用-1替代"<

Node[0]=0;//使Node[0]闲置

while

(1)

{NodeNum++;

cout<<"请输入第"<

";

cin>>Node[NodeNum];//输入结点值

if(Node[NodeNum]==-1)

break;

if(NodeNum==MaxSize)

{cout<<"输入字符个数超限,无法存储,请增大MaxSize的值"<

exit(0);

}

}

}

//输出树

voidDisplayTree()

{inti,j,k;//循环控制变量

inthight,level,SerialNum;//hight树高,level树层,SerialNum序号

//确定树的高度

for(i=0;i

{if(pow(2,i)>NodeNum)

break;

}

hight=i;

//从第1层至第hight层进行二叉树的输出

level=1;SerialNum=1;

while(level<=hight)

{//每层前端空格输出控制

for(i=0;i

cout<<"";

//每层输出的结点个数控制

for(j=0;j

{

if(Node[SerialNum]!

=0&&Node[SerialNum]!

=-1)

cout<

elseif(Node[SerialNum]==0)

cout<<"";//输出空格

else

break;//遇到结束符-1,跳出输出

for(k=0;k

cout<<"";//每层后端空格输出控制

}

cout<

level++;//层数增1

}

cout<

}

//输出数组

voidDisplayArray()

{inti=1;

do

{cout<

i++;

}while(Node[i]!

=-1);

cout<

}

//主函数

voidmain()

{CreateTreeArray();//创建二叉树结点的一维数组

cout<<"二叉树为"<

DisplayTree();//输出树

cout<<"结点数组为"<

DisplayArray();//输出数组

system("pause");

}

用一维数组表示二叉树,这一方法的优点是:

方法直观且容易实现,而且形象地输出二叉树的实际结构。

其缺点是:

访问、插入、删除操作难以实现,特别是当二叉树不是完全二叉树时,会占用过多的内存空间。

例如,存储以下含4结点的二叉树,却要多花费了8个存储单元,才能完整地表现这一结构。

6.3.2二叉树的链表表示法

二叉树的链表表示法是利用链表来存储二叉树,即运用动态存储结构和指针的方式来建立二叉树。

其结点结构如下:

*left

data

*right

二叉树的结点定义如下:

structBinTreeNode

{chardata;

structBinTreeNode*left,*right;

};

【范例5】对下图所示的二叉树

用链表来表示它,其结构图如下:

构建过程如下:

(1)先将倒数第一层的三个结点建子二叉树,并用相应的结点指针指向其根结点。

(2)再将二层的两个结点建子二叉树,并用相应的结点指针指向其根结点。

(3)最后将第一层的一个结点建子二叉树,并用一个结点指针指向其根结点。

上述子二叉树经过连接之后,便成了我们所需要构建的二叉树。

这种建二叉树的过程是,由叶子结点开始,向上逐层扩建成树。

【范例程序】用链表来表示二叉树。

/*

名称:

ch06_03.cpp

示范:

二叉树的链表表示法

*/

#include

usingnamespacestd;

//二叉树结点定义

structBinTreeNode

{chardata;//结点数据

structBinTreeNode*left,*right;//左、右指针

};

//建二叉树

BinTreeNode*MakeBinTree(chardata,BinTreeNode*left,BinTreeNode*right)

{//以data为数据,left,right为左右子树建树

BinTreeNode*newnode;

newnode=newBinTreeNode;

newnode->data=data;

newnode->left=left;

newnode->right=right;

returnnewnode;

}

//主函数

voidmain()

{BinTreeNode*aptr,*bptr,*cptr,*dptr,*eptr,*fptr;

dptr=MakeBinTree('D',NULL,NULL);

eptr=MakeBinTree('E',NULL,NULL);

fptr=MakeBinTree('F',NULL,NULL);

bptr=MakeBinTree('B',dptr,eptr);

cptr=MakeBinTree('C',NULL,fptr);

aptr=MakeBinTree('A',bptr,cptr);

system("pause");

}

从上述创建链式二叉树的程序可以发现,这种结构在程序实现上十分复杂,不便于实现其程序控制,而且还无法输出它的实际结构,也无法查找结点的父结点。

但这种结构也有其优点:

那就是查找、插入和删除操作方便。

二叉树结点值的输出需要用它二叉树的遍历,在下一节进行介绍。

6.4二叉树的遍历

遍历二叉树最简单的方法就是遍历树中所有结点各一次,并且在遍历后将树的数据转化为线性关系。

其实二叉树的遍历,并不像之前所提到的线性结构那样简单,就一个简单二叉树结点而言,每个结点都可以区分左、右两个分支。

如下图所示的二叉树共有ABC、ACB、BAC、BCA、CAB、CBA等6种遍历方式。

如果是依照二叉树特性一律由左向右遍历,那么只剩下三种遍历方式,即BAC、ABC、BCA。

以上三种遍历的方式的命名如下。

●中序遍历(BAC,Preorder):

左子树-树根-右子树。

●前序遍历(ABC,Inorder):

树根-左子树-右子树。

●后序遍历(BCA,Postorder):

左子树-右子树-树根。

所谓的中序、前序、后序都是针对根结点而言的,例如中序遍历是根结点在中间,前序遍历是根结点在前面,后序遍历是根结点在后面。

而遍历方式一定是先左子树后右子树。

6.4.1中序遍历

中序遍历的顺序为:

左子树-树根-右子树,即沿树的左子树一直往下,直到无法前进时退后到父结点,而后再往右子树一直往下。

如果右子树也走完了就退回上层的左结点,重复左、中、右的顺序遍历,下图是【范例5】中所给出的二叉树,它的中序遍历为:

DBEACF。

中序遍历的递归(回溯)算法如下:

voidInorder(BinTreeNode*ptr)

{if(ptr!

=NULL)

{Inorder(ptr->left);//遍历左子树

cout<data;//访问树根

Inorder(ptr->right);//遍历右子树

}

}

6.4.2前序遍历

前序遍历的顺序为:

树根-左子树-右子树,即从根结点开始处理,根结点处理完后往左子树走,直到无法前进再处理右子树。

【范例5】的前序遍历为ABDECF。

前序遍历的递归(回溯)算法如下:

voidPreorder(BinTreeNode*ptr)

{if(ptr!

=NULL)

{cout<data;//访问树根

Preorder(ptr->left);//遍历左子树

Preorder(ptr->right);//遍历右子树

}

}

6.4.3后序遍历

后序遍历的顺序为:

左子树-右子树-树根。

后序遍历和前序遍历相反,它是把左、右子树的结点都处理完才处理树根。

【范例5】的后序遍历为:

DEBFCA。

后序遍历的递归(回溯)算法如下:

voidPostorder(BinTreeNode*ptr)

{if(ptr!

=NULL)

{Postorder(ptr->left);//遍历左子树

Postorder(ptr->right);//遍历右子树

cout<data;//访问树根

}

}

6.4.4二叉树的遍历实例

对【范例5】中二叉树,进行中序、前序与后序遍历的操作。

并将其结果输出,请与上面的讨论加以对照。

【范例程序】对【范例5】中的二叉树进行中序、前序与后序遍历,并输出其结果。

/*

名称:

ch06_04.cpp

示范:

二叉树的中序、前序、后序遍历

*/

#include

usingnamespacestd;

//二叉树结点定义

structBinTreeNode

{chardata;//结点数据

structBinTreeNode*left,*right;//左、右指针

};

//建二叉树

BinTreeNode*MakeBinTree(chardata,BinTreeNode*left,BinTreeNode*right)

{//以data为数据,left,right为左右子树建树

BinTreeNode*newnode;

newnode=newBinTreeNode;

newnode->data=data;

newnode->left=left;

newnode->right=right;

returnnewnode;

}

//中序遍历

voidInorder(BinTreeNode*ptr)

{if(ptr!

=NULL)

{Inorder(ptr->left);//遍历左子树

cout<data;//访问树根

Inorder(ptr->right);//遍历右子树

}

}

//前序遍历

voidPreorder(BinTreeNode*ptr)

{if(ptr!

=NULL)

{cout<data;//访问树根

Preorder(ptr->left);//遍历左子树

Preorder(ptr->right);//遍历右子树

}

}

//后序遍历

voidPostorder(BinTreeNode*ptr)

{if(ptr!

=NULL)

{Postorder(ptr->left);//遍历左子树

Postorder(ptr->right);//遍历右子树

cout<data;//访问树根

}

}

//主函数

voidmain()

{BinTreeNode*aptr,*bptr,*cptr,*dptr,*eptr,*fptr;

dptr=MakeBinTree('D',NULL,NULL);

eptr=MakeBinTree('E',NULL,NULL);

fptr=MakeBinTree('F',NULL,NULL);

bptr=MakeBinTree('B',dptr,eptr);

cptr=MakeBinTree('C',NULL,fptr);

aptr=MakeBinTree('A',bptr,cptr);//aptr成为二叉树的根结点指针

cout

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 初中教育 > 语文

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2