第6章二叉树.docx

上传人:b****7 文档编号:9260935 上传时间:2023-02-03 格式:DOCX 页数:64 大小:43.26KB
下载 相关 举报
第6章二叉树.docx_第1页
第1页 / 共64页
第6章二叉树.docx_第2页
第2页 / 共64页
第6章二叉树.docx_第3页
第3页 / 共64页
第6章二叉树.docx_第4页
第4页 / 共64页
第6章二叉树.docx_第5页
第5页 / 共64页
点击查看更多>>
下载资源
资源描述

第6章二叉树.docx

《第6章二叉树.docx》由会员分享,可在线阅读,更多相关《第6章二叉树.docx(64页珍藏版)》请在冰豆网上搜索。

第6章二叉树.docx

第6章二叉树

第6章二叉树

在前面几章里讨论的数据结构都属于线性结构,线性结构的特点是逻辑结构简单,易于进行查找、插入和删除等操作,其主要用于对客观世界中具有单一的前驱和后继的数据关系进行描述,而现实中的许多事物的关系并非这样简单,如人类社会的族谱、各种社会组织机构以及城市交通、通讯等,这些事物中的联系都是非线性的,采用非线性结构进行描绘会更明确和便利。

所谓非线性结构是指,在该结构中至少存在一个数据元素,有两个或两个以上的直接前驱(或直接后继)元素。

树形结构和图形结构就是其中十分重要的非线性结构,可以用来描述客观世界中广泛存在的层次结构和网状结构的关系,如前面提到的族谱、城市交通等。

在本书的第6、7、8三章将重点讨论这两类非线性结构的有关概念、存储结构、在各种存储结构上所实施的一些运算以及有关的应用实例。

本章首先介绍树形结构中最简单、应用十分广泛的二叉树结构,然后对具有更一般意义的树结构进行讨论。

6.1二叉树的定义与性质

6.1.1二叉树的基本概念

⒈二叉树

二叉树(BinaryTree)是有限个元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成。

当集合为空时,称该二叉树为空二叉树。

在二叉树中,一个元素也称作一个结点。

二叉树是有序的,即若将其左、右子树颠倒,就成为另一棵不同的二叉树。

即使树中结点只有一棵子树,也要区分它是左子树还是右子树。

因此二叉树具有五种基本形态,如图6-1所示。

 

Ф

右子树

右子树

左子树

左子树

(a)(b)(c)(d)(e)

图6-1二叉树的五种基本形态

⒉二叉树的相关概念

⑴结点的度结点所拥有的子树的个数称为该结点的度。

⑵叶结点度为0的结点称为叶结点,或者称为终端结点。

⑶分枝结点度不为0的结点称为分支结点,或者称为非终端结点。

一棵树的结点除叶结点外,其余的都是分支结点。

⑷左孩子、右孩子、双亲、兄弟树中一个结点的子树的根结点称为这个结点的孩子。

在二叉树中,左子树的根称为左孩子,右子树的根称为右孩子。

这个结点称为它孩子结点的双亲。

具有同一个双亲的孩子结点互称为兄弟。

⑸路径、路径长度如果一棵树的一串结点n1,n2,…,nk有如下关系:

结点ni是ni+1的父结点(1≤i

这条路径的长度是k-1。

⑹祖先、子孙在树中,如果有一条路径从结点M到结点N,那么M就称为N的祖先,而N称为M的子孙。

⑺结点的层数规定树的根结点的层数为1,其余结点的层数等于它的双亲结点的层数加1。

⑻树的深度树中所有结点的最大层数称为树的深度。

⑼树的度树中各结点度的最大值称为该树的度。

⑽满二叉树在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层上,这样的一棵二叉树称作满二叉树。

如图6-2(a)所示。

若所有结点要么是含有左右子树的分支结点,要么是叶子结点,但由于其叶子未在同一层上,则不是满二叉树,如图6-2(b)所示。

1

1

A

A

2

B

3

2

3

C

B

C

4

5

6

7

7

6

5

4

G

F

E

D

D

E

G

F

H

I

K

J

H

I

O

N

M

L

8

9

14

15

13

12

11

10

9

8

(a)一棵满二叉树(b)一棵非满二叉树

图6-2满二叉树和非满二叉树示意图

⑾完全二叉树一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

完全二叉树的特点是:

叶子结点只能出现在最下层和次最下层,且最下层的叶子结点集中在树的左部。

显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。

如图6-3(a)所示为一棵完全二叉树,6-2(b)和图6-3(b)都不是完全二叉树。

 

1

1

A

A

3

1

3

2

C

B

C

B

6

5

4

7

6

5

4

E

F

D

F

G

E

D

7

G

J

I

H

10

8

9

(a)一棵完全二叉树(b)一棵非完全二叉树

图6-3完全二叉树和非完全二叉树示意图

6.1.2二叉树的主要性质

性质1:

一棵非空二叉树的第i层上最多有2i-1个结点(i≥1)。

该性质可由数学归纳法证明。

证明略。

性质2:

一棵深度为k的二叉树中,最多具有2k-1个结点。

证明:

设第i层的结点数为xi(1≤i≤k),深度为k的二叉树的结点数为M,由性质1可知,xi最多为2i-1,则有:

k

i=1

k

i=1

M=xi≤2i-1=2k-1

性质3:

对于一棵非空的二叉树,如果叶子结点数为n0,度数为2的结点数为n2,则有:

n0=n2+1。

证明:

设n为二叉树的结点总数,n1为二叉树中度为1的结点数,则有:

n=n0+n1+n2(6-1)

在二叉树中,除根结点外,其余结点都有惟一的一个进入分支。

设B为二叉树中的分支数,那么有:

B=n-1(6-2)

这些分支是由度为1和度为2的结点发出的,一个度为1的结点发出一个分支,一个度为2的结点发出两个分支,所以有:

B=n1+2n2(6-3)

综合(6-1)、(6-2)、(6-3)式可以得到:

n0=n2+1

性质4具有n个结点的完全二叉树的深度k为log2n+1。

证明根据完全二叉树的定义和性质2可知,当一棵完全二叉树的深度为k、结点个数为n时,有

2k-1-1

即2k-1≤n<2k

对不等式取对数,有

k-1≤log2n

由于k是整数,所以有k=log2n+1。

性质5对于具有n个结点的完全二叉树,如果按照从上至下和从左到右的顺序对二叉树中的所有结点从1开始顺序编号,则对于任意的序号为i的结点,有:

⑴如果i>1,则序号为i的结点的双亲结点的序号为i/2(“/”表示整除);如果i=1,则序号为i的结点是根结点,无双亲结点。

⑵如果2i≤n,则序号为i的结点的左孩子结点的序号为2i;如果2i>n,则序号为i的结点无左孩子。

⑶如果2i+1≤n,则序号为i的结点的右孩子结点的序号为2i+1;如果2i+1>n,则序号为i的结点无右孩子。

如果,若对二叉树的根结点从0开始编号,则相应的i号结点的双亲结点的编号为(i-1)/2,左孩子的编号为2i+1,右孩子的编号为2i+2。

此性质可采用数学归纳法证明。

证明略。

6.2二叉树的基本操作与存储实现

6.2.1二叉树的存储

⒈顺序存储结构

所谓二叉树的顺序存储,就是用一组连续的存储单元存放二叉树中的结点。

一般是按照二叉树结点从上至下、从左到右的顺序存储。

这样结点在存储位置上的前驱后继关系并不一定就是它们在逻辑上的邻接关系,然而只有通过一些方法确定某结点在逻辑上的前驱结点和后继结点,这种存储才有意义。

因此,依据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以惟一地反映出结点之间的逻辑关系,这样既能够最大可能地节省存储空间,又可以利用数组元素的下标值确定结点在二叉树中的位置,以及结点之间的关系。

图6-3(a)所示的完全二叉树的顺序存储示意如图6-4所示。

A

B

C

D

E

F

G

H

I

J

数组下标0123456789

图6-4完全二叉树的顺序存储示意图

对于一般的二叉树,如果仍按从上至下和从左到右的顺序将树中的结点顺序存储在一维数组中,则数组元素下标之间的关系不能够反映二叉树中结点之间的逻辑关系,只有增添一些并不存在的空结点,使之成为一棵完全二叉树的形式,然后再用一维数组顺序存储。

如图6-5给出了一棵一般二叉树改造后的完全二叉树形态和其顺序存储状态示意图。

显然,这种存储对于需增加许多空结点才能将一棵二叉树改造成为一棵完全二叉树的存储时,会造成空间的大量浪费,不宜用顺序存储结构。

最坏的情况是右单支树,如图6-6所示,一棵深度为k的右单支树,只有k个结点,却需分配2k-1个存储单元。

 

A

A

C

B

C

B

E

D

 

D

E

G

 

F

F

G

 

(a)一棵二叉树(b)改造后的完全二叉树

A

B

C

D

E

F

G

(c)改造后完全二叉树顺序存储状态

图6-5一般二叉树及其顺序存储示意图

A

A

B

B

C

 

C

D

D

 

 

(a)一棵右单支二叉树(b)改造后的右单支树对应的完全二叉树

A

B

C

D

(c)单支树改造后完全二叉树的顺序存储状态

图6-6右单支二叉树及其顺序存储示意图

二叉树的顺序存储表示可描述为:

#defineMAXNODE…//二叉树的最大结点数

typedefDataTypeSqBiTree[MAXNODE]//0号单元存放根结点

SqBiTreebt;

即将bt定义为含有MAXNODE个DataType类型元素的一维数组。

⒉链式存储结构

所谓二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。

通常有下面两种形式。

⑴二叉链表存储

链表中每个结点由三个域组成,除了数据域外,还有两个指针域,分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。

结点的存储的结构为:

lchild

rchild

data

 

其中,data域存放某结点的数据信息;lchild与rchild分别存放指向左孩子和右孩子的指针,当左孩子或右孩子不存在时,相应指针域值为空(用符号∧或NULL表示)。

图6-7(a)给出了图6-3(b)所示的一棵二叉树的二叉链表示。

二叉链表也可以带头结点的方式存放,如图6-7(b)所示。

头指针bt头结点指针bt

A

A

 

C

B

C

B

 

F

E

D

F

E

D

G

G

(a)不带头结点的二叉链表(b)带头结点的二叉链表

图6-7图6-3(b)所示二叉树的二叉链表示意图

⑵三叉链表存储

每个结点由四个域组成,具体结构为:

parent

rchild

lchild

data

其中,data、lchild以及rchild三个域的意义同二叉链表结构;parent域为指向该结点双亲结点的指针。

这种存储结构既便于查找孩子结点,又便于查找双亲结点;但是,相对于二叉链表存储结构而言,它增加了空间开销。

图6-8给出了图6-3(b)所示的二叉树的三叉链表示。

 

A

 

C

B

 

F

E

D

 

G

 

图6-8图6-3(b)所示二叉树的三叉链表表示示意图

尽管在二叉链表中无法由结点直接找到其双亲,但由于二叉链表结构灵活,操作方便,对于一般情况的二叉树,甚至比顺序存储结构还节省空间。

因此,二叉链表是最常用的二叉树存储方式。

如不加特殊说明,本书后面所涉及到的二叉树的链式存储结构都是指二叉链表结构。

二叉树的二叉链表存储表示可描述为:

typedefstructBiTNode{

DataTypedata;

structBiTNode*lchild;*rchild;//左右孩子指针

}BiTNode,*BiTree;

即将BiTree定义为指向二叉链表结点结构的指针类型。

6.2.2二叉树的基本操作及实现

⒈二叉树的基本操作

二叉树的基本操作通常有以下几种:

⑴Initiate(bt):

建立一棵空二叉树。

⑵Create(x,lbt,rbt):

生成一棵以x为根结点的数据域信息,以二叉树lbt和rbt为左子树和右子树的二叉树。

⑶InsertL(bt,x,parent):

将数据域信息为x的结点插入到二叉树bt中作为结点parent的左孩子结点。

如果结点parent原来有左孩子结点,则将结点parent原来的左孩子结点作为结点x的左孩子结点。

⑷InsertR(bt,x,parent):

将数据域信息为x的结点插入到二叉树bt中作为结点parent的右孩子结点。

如果结点parent原来有右孩子结点,则将结点parent原来的右孩子结点作为结点x的右孩子结点。

⑸DeleteL(bt,parent):

在二叉树bt中删除结点parent的左子树。

⑹DeleteR(bt,parent):

在二叉树bt中删除结点parent的右子树。

⑺Search(bt,x):

在二叉树bt中查找数据元素x。

⑻Traverse(bt):

按某种方式遍历二叉树bt的全部结点。

⒉算法的实现

算法的实现依赖于具体的存储结构,当二叉树采用不同的存储结构时,上述各种操作的实现算法是不同的。

下面讨论基于二叉链表存储结构的上述操作的实现算法。

⑴Initiate(bt)初始建立二叉树bt,并使bt指向头结点。

在二叉树根结点前建立头结点,就如同在单链表前建立的头结点,可以方便后边的一些操作实现。

intInitiate(BiTree*bt){

//初始建立一棵带头结点的二叉树

bt=newBiTNode;

if(!

bt)

return0;

*bt->lchild=NULL;

*bt->rchild=NULL;

return1;

}

【算法6-1】建立二叉树算法

在主函数中,可以通过如下方式调用Initiate函数:

main()

{BiTreet;

Initiate(&t);

}

⑵Create(x,lbt,rbt)建立一棵以x为根结点的数据域信息,以二叉树lbt和rbt为左右子树的二叉树。

建立成功时返回所建二叉树结点的指针;建立失败时返回空指针。

BiTreeCreate(DataTypex,BiTreelbt,BiTreerbt){

//生成一棵以x为根结点的数据域值以lbt和rbt为左右子树的二叉树

BiTreep;

P=newBiTNode;

if(!

p)

returnNULL;

p->data=x;

p->lchild=lbt;

p->rchild=rbt;

returnp;

}

【算法6-2】建立一棵已生成左右子树的二叉树的算法

⑶InsertL(bt,x,parent)

BiTreeInsertL(BiTreebt,DataTypex,BiTreeparent){

//在二叉树bt中的parent所指结点和其左子树之间插入数据元素为x的结点

BiTreep;

if(parent==NULL)

{cout<<”插入出错”<

returnNULL;

}

p=newBiTNode;

if(!

p)

returnNULL;

p->data=x;

p->lchild=NULL;

p->rchild=NULL;

if(parent->lchild==NULL)

parent->lchild=p;

else

{p->lchild=parent->lchild;

parent->lchild=p;

}

returnbt;

}

【算法6-3】给二叉树某结点插入一个左孩子算法

⑷InsertR(bt,x,parent)功能类同于⑶,算法略。

⑸DeleteL(bt,parent)在二叉树bt中删除结点parent的左子树。

当parent或parent的左孩子结点为空时删除失败。

删除成功时返回根结点指针;删除失败时返回空指针。

BiTreeDeleteL(BiTreebt,BiTreeparent){

//在二叉树bt中删除结点parent的左子树

BiTreep;

if(parent==NULL||parent->lchild==NULL)

{cout<<”删除出错”<

returnNULL;

}

p=parent->lchild;

parent->lchild=NULL;

deletep;//当p为非叶子结点时,这样删除仅释放了所删子树根结点的空间,

//若要删除子树分支中的结点,需用后面介绍的遍历操作来实现。

returnbt;

}

【算法6-4】删除二叉树中某结点的左子树算法

⑹DeleteR(bt,parent)功能类同于⑸,只是删除结点parent的右子树。

算法略。

操作Search(bt,x)实际是遍历操作Traverse(bt)的特例,关于二叉树的遍历操作的实现,将在下一节中重点介绍。

6.3二叉树的遍历

6.3.1二叉树的遍历方法及递归实现

二叉树的遍历是指按照某种顺序访问二叉树中的每个结点,使每个结点被访问一次且仅被访问一次。

遍历是二叉树中经常要用到的一种操作。

因为在实际应用问题中,常常需要按一定顺序对二叉树中的每个结点逐个进行访问,或查找具有某一特点的结点,然后对这些满足条件的结点进行处理。

通过一次完整的遍历,可使二叉树中结点信息由非线性排列变为某种意义上的线性序列。

也就是说,遍历操作使非线性结构线性化。

由二叉树的定义可知,一棵二叉树由根结点、根结点的左子树和根结点的右子树三部分组成。

因此,只要依次遍历这三部分,就可以遍历整个二叉树。

若以D、L、R分别表示访问根结点、遍历根结点的左子树、遍历根结点的右子树,则二叉树的遍历方式有六种:

DLR、LDR、LRD、DRL、RDL和RLD。

如果限定先左后右,则只有前三种方式,即DLR(称为先序遍历)、LDR(称为中序遍历)和LRD(称为后序遍历)。

⒈先序遍历(DLR)

先序遍历的递归过程为:

若二叉树为空,遍历结束。

否则,

⑴访问根结点;

⑵先序遍历根结点的左子树;

⑶先序遍历根结点的右子树。

先序遍历二叉树的递归算法如下:

voidPreOrder(BiTreebt){

//先序遍历二叉树bt

if(bt==NULL)

return;//递归调用的结束条件

Visit(bt->data);//访问结点的数据域

PreOrder(bt->lchild);//先序递归遍历bt的左子树

PreOrder(bt->rchild);//先序递归遍历bt的右子树

}

【算法6-5】二叉树先序遍历算法

对于图6-3(b)所示的二叉树,按先序遍历所得到的结点序列为:

ABDGCEF

⒉中序遍历(LDR)

中序遍历的递归过程为:

若二叉树为空,遍历结束。

否则,

⑴中序遍历根结点的左子树;

⑵访问根结点;

⑶中序遍历根结点的右子树。

中序遍历二叉树的递归算法如下:

voidInOrder(BiTreebt){

//中序遍历二叉树bt

if(bt==NULL)

return;//递归调用的结束条件

InOrder(bt->lchild);//中序递归遍历bt的左子树

Visit(bt->data);//访问结点的数据域

InOrder(bt->rchild);//中序递归遍历bt的右子树

}

【算法6-6】二叉树中序遍历算法

对于图6-3(b)所示的二叉树,按中序遍历所得到的结点序列为:

DGBAECF

⒊后序遍历(LRD)

后序遍历的递归过程为:

若二叉树为空,遍历结束。

否则,

⑴后序遍历根结点的左子树;

⑵后序遍历根结点的右子树。

⑶访问根结点;

后序遍历二叉树的递归算法如下:

voidPostOrder(BiTreebt){

//后序遍历二叉树bt

if(bt==NULL)

return;//递归调用的结束条件

PostOrder(bt->lchild);//后序递归遍历bt的左子树

PostOrder(bt->rchild);//后序递归遍历bt的右子树

Visite(bt->data);//访问结点的数据域

}

【算法6-7】二叉树后序遍历算法

对于图6-3(b)所示的二叉树,按后序遍历所得到的结点序列为:

GDBEFCA

⒋层次遍历

所谓二叉树的层次遍历,是指从二叉树的第一层(根结点)开始,从上至下逐层遍历,在同一层中,则按从左到右的顺序对结点逐个访问。

对于图6-3(b)所示的二叉树,按层次遍历所得到的结点序列为:

ABCDEFG

下面讨论层次遍历的算法。

由层次遍历的定义可以推知,在进行层次遍历时,对一层结点访问完后,再按照它们的访问次序对各个结点的左孩子和右孩子顺序访问,这样一层一层进行,先遇到的结点先访问,这与队列的操作原则比较吻合。

因此,在进行层次遍历时,可设置一个队列结构,遍历从二叉树的根结点开始,首先将根结点指针入队列,然后从队头取出一个元素,每取一个元素,执行下面两个操作:

⑴访问该元素所指结点;

⑵若该元素所指结点的左、右孩子结点非空,则将该元素所指结点的左孩子指针和右孩子指针顺序入队。

此过程不断进行,当队列为空时,二叉树的层次遍历结束。

在下面的层次遍历算法中,二叉树以二叉链表存放,一维数组Queue[MAXNODE]用以实现队列,变量front和rear分别表示队头和队尾指针,记录当前对头元素和队尾元素在数组中的位置。

voidLevelOrder(BiTreebt){

//层次遍历二叉树bt

BiTreeQueue[MAXNODE];

intfront,rear;

if(bt==NULL)

return;

front=-1;

rear=0;

queue[rear]=bt;

while(front!

=rear)

{front++;

Visit(queue[front]->data);//访问队首结点的数据域

if(queue[front]->lchild!

=NULL)//将队首结点的左孩子结点入队列

{rear++;

queue[rear]=queue[front]->lchild;

}

if(queue[front]->rchild!

=NULL)//将队首结点的右孩子结点入队列

{rear++;

queue[rear]=queue[front]->rchild;

}

}

}

【算法6-8】二叉树层次遍历算法

6.3.2二叉树遍历的非递归实现

前面给出的二叉树先序、中序和后序三种遍历算法都是递归算法。

当给出二叉树的链式存储结构以后,用具有递归功能的程序设计语言很方便就能实现上述算法。

然而,并非所有程序设计语言都允许递归;另一方面,递归程序虽然简洁,但可读性一般不好,执行效率也不高。

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

当前位置:首页 > 医药卫生 > 药学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1