遍历二叉树的非递归算法.docx

上传人:b****6 文档编号:6818431 上传时间:2023-01-10 格式:DOCX 页数:34 大小:103.59KB
下载 相关 举报
遍历二叉树的非递归算法.docx_第1页
第1页 / 共34页
遍历二叉树的非递归算法.docx_第2页
第2页 / 共34页
遍历二叉树的非递归算法.docx_第3页
第3页 / 共34页
遍历二叉树的非递归算法.docx_第4页
第4页 / 共34页
遍历二叉树的非递归算法.docx_第5页
第5页 / 共34页
点击查看更多>>
下载资源
资源描述

遍历二叉树的非递归算法.docx

《遍历二叉树的非递归算法.docx》由会员分享,可在线阅读,更多相关《遍历二叉树的非递归算法.docx(34页珍藏版)》请在冰豆网上搜索。

遍历二叉树的非递归算法.docx

遍历二叉树的非递归算法

目录

6.1树的定义和基本术语0

6.2二叉树1

6.2.1二叉树的定义1

6.2.2二叉树的性质3

6.2.3二叉树的存储结构4

6.3树和森林5

6.4二叉树的先|中|后序遍历算法6

6.5先|后|中序遍历的应用扩展8

6.5.1基于先序遍历的二叉树(二叉链)的创建8

6.5.2统计二叉树中叶子结点的数目8

6.5.3求二叉树的高度9

6.5.4释放二叉树的所有结点空间10

6.5.5删除并释放二叉树中以元素值为x的结点作为根的各子树11

6.5.6求位于二叉树先序序列中第k个位置的结点的值11

6.5.7线索二叉树12

6.5.8树和森林的遍历13

6.6二叉树的层次遍历15

6.7判断一棵二叉树是否为完全二叉树16

6.8哈夫曼树及其应用17

6.8.1最优二叉树(哈夫曼树)17

6.8.2哈夫曼编码17

6.9遍历二叉树的非递归算法18

6.9.1先序非递归算法18

6.9.2中序非递归算法18

6.9.3后序非递归算法19

第6章二叉树和树

6.1树的定义和基本术语

1、树的递归定义

1)结点数n=0时,是空树

2)结点数n>0时

有且仅有一个根结点、m个互不相交的有限结点集——m棵子树

2、基本术语

结点:

叶子(终端结点)、根、内部结点(非终端结点、分支结点);

树的规模:

结点的度、树的度、结点的层次、树的高度(深度)

结点间的关系:

双亲

(1)—孩子(m),祖先—子孙,兄弟,堂兄弟

兄弟间是否存在次序:

无序树、有序树

去掉根结点

非空树森林

引入一个根结点

3、树的抽象数据类型定义

树特有的操作:

查找:

双亲、最左的孩子、右兄弟

结点的度不定,给出这两种操作可以查找到一个结点的全部孩子

插入、删除:

孩子

遍历:

存在一对多的关系,给出一种有规律的方法遍历(有且仅访问一次)树中的结点

ADTTree{

数据对象:

D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}

数据关系:

若D为空集,则称为空树;

若D仅含一个数据元素,则R为空集,否则R={H},H是如下二元关系:

(1)在D中存在唯一的称为根的数据元素root,它在关系H下无前驱;

(2)若D-{root}≠Ф,则存在D-{root}的一个划分D1,D2,…,Dm(m>0)(Di表示构成第i棵子树的结点集),对任意j≠k(1≤j,k≤m)有Dj∩Dk=Ф,且对任意的i(1≤i≤m),唯一存在数据元素xi∈Di,有∈H(H表示结点之间的父子关系);

(3)对应于D-{root}的划分,H-{,…,}有唯一的一个划分H1,H2,…,Hm(m>0)(Hi表示第i棵子树中的父子关系),对任意j≠k(1≤j,k≤m)有Hj∩Hk=Ф,且对任意i(1≤i≤m),Hi是Di上的二元关系,(Di,{Hi})是一棵符合本定义的树,称为根root的子树。

基本操作:

InitTree(&T)

操作结果:

构造空树T

DestroyTree(&T)

初始条件:

树T已存在

操作结果:

销毁树T

ClearTree(&T)

初始条件:

树T已存在

操作结果:

将树T清为空树

TreeEmpty(T)

初始条件:

树T已存在

操作结果:

若T为空树,则返回TRUE,否则返回FALSE

TreeDepth(T)

初始条件:

树T已存在

操作结果:

返回树T的深度

Root(T)

初始条件:

树T已存在

操作结果:

返回T的根

Value(T,cur_e)

初始条件:

树T已存在,cur_e是T中某个结点

操作结果:

返回cur_e的值

Assign(T,&cur_e,value)

初始条件:

树T已存在,cur_e是T中某个结点

操作结果:

结点cur_e赋值为value

Parent(T,cur_e)

初始条件:

树T已存在,cur_e是T中某个结点

操作结果:

若cur_e是T的非根结点,则返回它的双亲,否则函数值为“空”

LeftChild(T,cur_e)

初始条件:

树T已存在,cur_e是T中某个结点

操作结果:

若cur_e是T的非叶子结点,则返回它的最左孩子,否则返回“空”

RightSibling(T,cur_e)

初始条件:

树T已存在,cur_e是T中某个结点

操作结果:

若cur_e有右兄弟,则返回它的右兄弟,否则返回“空”

InsertChild(&T,p,i,c)

初始条件:

树T已存在,p指向T中某个结点,1≤i≤p所指结点的度+1,非空树c与T不相交

操作结果:

插入c为T中p所指结点的第i棵子树。

DeleteChild(&T,p,i)

初始条件:

树T已存在,p指向T中某个结点,1≤i≤p所指结点的度

操作结果:

删除T中p所指结点的第i棵子树。

TraverseTree(T,visit())

初始条件:

树T已存在,visit是对结点操作的应用函数

操作结果:

按某种次序对T的每个结点调用函数visit()一次且至多一次。

一旦visit()失败,则操作失败

}ADTTree

6.2二叉树

一般树的度不定,直接考虑其操作比较困难,故首先考虑度为二的树。

这里引入二叉树。

6.2.1二叉树的定义

1、二叉树的特殊性

·0≤度≤2

·子树有左右之分(子树的个数=1或2时)

注意:

0≤度≤2的有序树≠二叉树

当某个结点只有一棵子树时,不存在序的概念

2、二叉树的抽象数据类型定义

二叉树相对树的特殊性:

最左的孩子、右兄弟左孩子、右孩子

遍历的规律性:

L(左子树)、D(根)、R(右子树)的排列上

限定为L在R前访问(有对称关系的,只须考虑其中的一种)

ADTBinaryTree{

数据对象:

D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}

数据关系:

若D为空集,则称为空树;

若D仅含一个数据元素,则R为空集,否则R={H},H是如下二元关系:

(1)在D中存在唯一的称为根的数据元素root,它在关系H下无前驱;

(2)若D-{root}≠Ф,则存在D-{root}的一个划分DL,DR(左、右子树的结点集),且DL∩DR=Ф;

(3)若DL≠Ф,则DL中存在唯一元素xL,有∈H(H表示结点之间的父子关系),且存在DL上的关系HL

H;若DR≠Ф,则DR中存在唯一元素xR,有∈H,且存在DR上的关系HR

H;

(3)(DL,{HL})是一棵符合本定义的二叉树,称为根的左子树;(DR,{HR})是一棵符合本定义的二叉树,称为根的右子树。

基本操作:

InitBiTree(&T)

操作结果:

构造空二叉树T

DestroyBiTree(&T)

初始条件:

二叉树T已存在

操作结果:

销毁二叉树T

ClearBiTree(&T)

初始条件:

二叉树T已存在

操作结果:

将二叉树T清为空树

BiTreeEmpty(T)

初始条件:

二叉树T已存在

操作结果:

若T为空二叉树,则返回TRUE,否则返回FALSE

BiTreeDepth(T)

初始条件:

二叉树T已存在

操作结果:

返回二叉树T的深度

Root(T)

初始条件:

二叉树T已存在

操作结果:

返回T的根

Value(T,cur_e)

初始条件:

二叉树T已存在,cur_e是T中某个结点

操作结果:

返回cur_e的值

Assign(T,&cur_e,value)

初始条件:

二叉树T已存在,cur_e是T中某个结点

操作结果:

结点cur_e赋值为value

Parent(T,cur_e)

初始条件:

二叉树T已存在,cur_e是T中某个结点

操作结果:

若cur_e是T的非根结点,则返回它的双亲,否则函数值为“空”

LeftChild(T,cur_e)

初始条件:

二叉树T已存在,cur_e是T中某个结点

操作结果:

若cur_e是T的非叶子结点,则返回它的左孩子,否则返回“空”

RightChild(T,cur_e)

初始条件:

二叉树T已存在,cur_e是T中某个结点

操作结果:

若cur_e有右孩子,则返回它的右孩子,否则返回“空”

LeftSibling(T,cur_e)

初始条件:

二叉树T已存在,cur_e是T中某个结点

操作结果:

返回cur_e的左兄弟,若cur_e是T的左孩子或无左兄弟,则返回“空”

RightSibling(T,cur_e)

初始条件:

二叉树T已存在,cur_e是T中某个结点

操作结果:

返回cur_e的右兄弟,若cur_e是T的右孩子或无右兄弟,则返回“空”

InsertChild(&T,p,LR,c)

初始条件:

二叉树T已存在,p指向T中某个结点,LR为0或1,非空二叉树c与T不相交

操作结果:

插入c为T中p所指结点的左或右子树。

p所指结点的原有左或右子树则成为c的右子树

DeleteChild(&T,p,LR)

初始条件:

二叉树T已存在,p指向T中某个结点,LR为0或1

操作结果:

根据LR为0或1,删除T中p所指结点的左或右子树。

PreOrderTraverse(T,visit())

初始条件:

二叉树T已存在,visit是对结点操作的应用函数

操作结果:

先序遍历T,对每个结点调用函数visit()一次且仅一次。

一旦visit()失败,则操作失败

InOrderTraverse(T,visit())

初始条件:

二叉树T已存在,visit是对结点操作的应用函数

操作结果:

中序遍历T,对每个结点调用函数visit()一次且仅一次。

一旦visit()失败,则操作失败

PostOrderTraverse(T,visit())

初始条件:

二叉树T已存在,visit是对结点操作的应用函数

操作结果:

后序遍历T,对每个结点调用函数visit()一次且仅一次。

一旦visit()失败,则操作失败

LevelOrderTraverse(T,visit())

初始条件:

二叉树T已存在,visit是对结点操作的应用函数

操作结果:

层序遍历T,对每个结点调用函数visit()一次且仅一次。

一旦visit()失败,则操作失败

}ADTBinaryTree

6.2.2二叉树的性质

1、性质1:

第i层至多有2i-1个结点(由每个结点最多只有2个孩子推出)

2、性质2:

深度为k的二叉树至多有2k-1个结点(由性质1,将各层最多的结点数累加,再结合等比数列的求和得出)

思考:

深度为k的二叉树至少有多少个结点?

(k个)深度为k的b叉树至多/至少有多少个结点?

((bk-1)/(b-1),k)

3、性质3:

n0=n2+1(ni表示二叉树中度为i的结点个数)

从两个角度考虑:

二叉树中结点的构成(根据度)n=n0+n1+n2

二叉树中充当其余结点的孩子的结点数n-1(去掉根)=n1+2×n2

满二叉树:

达到性质1,2中所述的最大值情况

完全二叉树:

去掉最下一层的结点,其余结点形成一棵满二叉树;叶子集中在最下2层(或1层),最下一层的结点总是尽可能地占满左边的位置

4、性质4:

具有n个结点的完全二叉树的深度为

5、性质5:

结点间的编号关系

考虑二叉树的顺序映像问题,寻求一种将二叉树映像为向量的方法:

对完全二叉树从上至下,从左至右,从根开始依次编号(1..n)。

孩子编号

双亲编号

求双亲

i

i/2(>0)

求孩子

左:

2*i(

2*i+1(

i

右孩子编号

左孩子编号

求左兄弟

i(奇数,i>1)

i-1(>0)

求右兄弟

i+1(

i(偶数,i

思考:

满k叉树中结点间的编号关系?

孩子编号

双亲编号

求双亲

p

(k≥2)

求第i个孩子

p·k+i-(k-1)(

p

结点

结点的左兄弟

求左兄弟

p((p-1)%k≠1)

p-1(>0)

求右兄弟

p+1(

p((p-1)%k≠0)

6.2.3二叉树的存储结构

1、二叉树的顺序存储结构

1)方法

二叉树→补虚结点形成完全二叉树→自上而下、自左至右存储

2)类型定义

#defineMAX_TREE_SIZE100/*二叉树的最大结点数*/

typedefElemTypeSqBiTree[MAX_TREE_SIZE];/*0号单元存储根结点*/

必须引入特殊符号表示虚结点的值

上述类型定义的缺陷:

未指明实际二叉树占用的长度,可改进为:

typedefstruct{

ElemTypeelem[MAX_TREE_SIZE+1];/*1号单元存储根结点*/

intlength;

}SqBiTree;

3)不足:

空间的利用率不高

如:

若深度为5且仅含有5个结点的二叉树,必须要占用24~25-1空间。

rchild

lchild

parent

2、二叉树的链式存储结构

1)引入辅助空间表示结点间的相对关系

双亲

(1)——孩子

(2)(如右图)

lchild

data

rchild

parent

lchild

data

rchild

二叉链表三叉链表

若需要找指定结点的双亲,则用三叉链表可在O

(1)时间内获得某结点的双亲;而用二叉链表则需从根开始,采用一定的巡查方法进行搜索。

2)二叉链表的类型定义

typedefstructBiTNode{

ElemTypedata;

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

}BiTNode,*BiTree;

3)二叉链表的链域

若有n个结点,则共有2n个链域;其中n-1不为空,指向孩子。

4)二叉树(链式存储)的创建

输入序列与二叉树的映射关系

(1)完全二叉树的顺序映射

通过补虚结点,将一般的二叉树转变成完全二叉树,再对该完全二叉树的结点按自上而下、自左至右进行输入。

(2)二叉树的先序遍历

通过补虚结点,使二叉树中各实际结点均具有左右孩子,再对该二叉树按先序遍历进行输入。

(3)二叉树的两种遍历序列:

先序+中序,后序+中序

6.3树和森林

1、树的存储结构

1)双亲表示法

针对每一结点,附设指示其双亲位置的数据域。

采用顺序表(非顺序映像)。

#defineMAX_TREE_SIZE100/*树的最大结点数*/

typedefstructPTNode{

ElemTypedata;

intparent;

}PTNode;

typedefstruct{

PTNodenodes[MAX_TREE_SIZE];

intn;/*结点数*/

}PTree;

2)孩子表示法

各结点的孩子数是不定的,用顺序表表示必须给出树的度的最大值,以及每一结点的实际度数,空间浪费大。

故以链表存储每一结点的所有孩子的位置信息。

typedefstructCTNode{/*孩子结点*/

intchild;/*孩子结点的位置编号*/

structCTNode*next;/*下一个孩子结点*/

}*ChildPtr;

typedefstruct{

TElemTypedata;

ChildPtrfirstchild;/*孩子链表的头指针*/

}CTBox;

typedefstruct{

CTBoxnodes[MAX_TREE_SIZE];

intn,r;/*结点数和根的位置*/

}CTree;

3)孩子兄弟法

二叉链表表示。

针对每一结点,引入其第一个孩子和下一个右兄弟的位置域。

typedefstructCSNode{

ElemTypedata;

structCSNode*firstchild,*nextsibling;/*第一个孩子、下一个兄弟指针*/

}CSNode,*CSTree;

2、森林与二叉树的转换

森林用孩子兄弟法表示,形成二叉链表,可以将它理解为一个二叉树的二叉链表;

二叉树用二叉链表表示,可以将该二叉链表理解为孩子兄弟链表,从而获得森林。

6.4二叉树的先|中|后序遍历算法

1、遍历

·对于二叉树中的结点,有且仅访问一次

·遍历的规律性:

L(左子树)、D(根)、R(右子树)的排列上

限定L在R前访问(有对称关系的,只须考虑其中的一种)

·先(根)序遍历DLR

·中(根)序遍历LDR

·后(根)序遍历LRD

2、二叉树遍历的递归实现

二叉树的递归定义性质,决定了它的很多算法都可用递归实现,遍历就是其中之一。

对于二叉树的遍历,可以不去具体考虑各子问题(左子树、根、右子树)的遍历结果是什么,而去考虑如何由各子问题的求解结果构成原问题(二叉树)的遍历结果——递归规律的确定。

必须注意的是,当二叉树小到一定程度,即空树时,应直接给出解答——递归结束条件及处理。

三种遍历的区别(右图):

所经过的搜索路线是相同的;只是访问结点的时机不同。

每一结点在整个搜索路线中会经过3次(第一次进入到该结点、由左子树回溯到该结点、由右子树回溯到该结点),如在第一次遇到时就访问该结点,那么称之为先序;第二次经过时访问为中序;第三次经过时访问则为后序。

1)先序遍历

StatusPreOrderTraverse(BiTreeT,Status(*Visit)(ElemTypee)){

if(T!

=NULL){

if(Visit(T->data))

if(PreOrderTraverse(T->lchild,Visit))

if(PreOrderTraverse(T->rchild,Visit))

returnOK;

returnERROR;

}

elsereturnOK;

}

2)后序遍历

StatusPostOrderTraverse(BiTreeT,Status(*Visit)(ElemTypee)){

if(T!

=NULL){

if(PostOrderTraverse(T->lchild,Visit))

if(PostOrderTraverse(T->rchild,Visit))

if(Visit(T->data))

returnOK;

returnERROR;

}

elsereturnOK;

}

3)中序遍历

StatusInOrderTraverse(BiTreeT,Status(*Visit)(ElemTypee)){

if(T!

=NULL){

if(InOrderTraverse(T->lchild,Visit))

if(Visit(T->data))

if(InOrderTraverse(T->rchild,Visit))

returnOK;

returnERROR;

}

elsereturnOK;

}

6.5先|后|中序遍历的应用扩展

利用二叉树的遍历算法,适当修改访问结点操作的内容,可以得到求解许多问题的算法。

✧二叉树的创建(基于先序遍历)

✧二叉树的线索化

✧查找指定结点:

在访问结点时,判断当前访问的结点是否是指定的结点,若是则返回该结点位置,否则继续遍历;

✧查找位于某种遍历次序中的第k位的结点:

遍历前,引入一计数器,用来统计已访问过的结点数,初值为0;在访问结点时,将该计数器增1,并看是否达到k,……;

✧求叶子结点的数目:

遍历前,引入叶子结点计数器,初值为0;访问结点时,将该计数器增1;遍历结束,则计数器中的值即为所求解;

✧判断二叉树中是否仅包含有度为2和0的结点:

访问结点时,判断该结点孩子的有无情况,……

✧求指定结点的层次:

孩子结点的层次比其双亲结点层次多一;

✧求二叉树的高度:

二叉树的高度=max(左子树的高度,右子树的高度)+1

6.5.1基于先序遍历的二叉树(二叉链)的创建

【本例特征】

如何基于二叉树的先序、中序、后序遍历的递归算法进行问题求解?

【思路】

先序遍历PreOrderTraverse

创建CreateBiTree

输入

二叉链表示的二叉树的头指针T

带虚结点的先序序列ch

输入的表现方式

参数

由输入设备输入scanf(&ch)

输出

对结点的访问结果

二叉链表示的二叉树的头指针T

输出的表现方式

由输出设备输出

变参

空树(递归结束)的条件及处理

(直接求解)

T==NULL

ch==END_DATA(表示虚结点的值)

T=NULL

根结点的访问

(子问题直接求解)

Visit(T->data)

T=(BiTree)malloc(sizeof(BiTNode))

T->data=ch

左子树

(使用递归调用的解)

PreOrderTraverse(T->lchild)

CreateBiTree(T->lchild)

右子树

(使用递归调用的解)

PreOrderTraverse(T->rchild)

CreateBiTree(T->rchild)

【算法】见《数据结构(C语言版)》P131算法6.4。

6.5.2统计二叉树中叶子结点的数目

【本例特征】

如何通过全局变量、变参、返回值三种渠道返回处理结果?

【思路】

在遍历二叉树时,对一些特殊的结点(无左右孩子)进行计数。

可以修改遍历算法的结点访问操作为对特殊结点的判定和计数过程,需要注意的是计数器的处理方式。

可以有以下几种计数处理:

·用遍历函数的返回值传出求得的叶子结点的数目;

·为遍历函数增加一个引用参数,用来传出指定二叉树的叶子结点数目。

·引入全局的计数器,初始为0;

此处,遍历次序的选择对本算法没有太大影响。

【算法1全局的计数器】

//n为叶子结点的计数器

intn=0;

voidleaf(BiTreeT)

{//利用二叉树的先序遍历

if(T!

=NULL){

//访问结点->叶子的判定和计数

if(T->lchild==NULL&&T->rchild==NULL)n++;

leaf(T->lchild);

leaf(T->rchild);

}

}//调用结束,即可由n获得二叉树T的叶子结点数目,需注意下次调用前须n=0;

【算法2以函数返回值返回】

//函数值为T的叶子结点数

intleaf(BiTreeT)

{//利用二叉树的中序遍历,n为局部变量

n

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

当前位置:首页 > 党团工作 > 思想汇报心得体会

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

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