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