二叉树的存储结构
1.顺序存储结构
用一组连续的存储单元(一维数组)存储二叉树中元素,称为二叉树的顺序存储结构。描述如下:
#define maxsige 1024 //二叉树结点数最大值//
typedef datatype sqtree [maxsize]
若说明sqtree bt 则( bt[o] bt[1] … bt[maxsize-1])为二叉树的存储空间。每个单元bt[i] 可存放类型为datatype的树中元素。
2.链式存储结构
二叉结中结点的一般形式为:
lchild
data
rchild
遍历的递归算法
void preorder( BTptr T) //对当前根结点指针为T的二叉树按前序遍历//
if (T) visit(T) // 访问T所指结点 //
preorder(T&ndash>Lchild) //前序遍历T之左子树//
preorder(T&ndash>Rchild) //前序遍历T之右子树//
void Inorder (BTptr T) //对当前根结点指针为T的二叉树按中序遍历//
if (T) Inorder( T->Lchild) //中序遍历T之左子树//
visit(T) //访问T所指结点//
Inorder(T->Rchild) //中序遍历T之右子树//
void postorder ( BTptr T) //对当前根结点指针为T的二叉树按后序遍历//
if (T) postorder(T&ndash>Lchild) //后序遍历T之左子树//
postorder(T&ndash>Rchild) //后序遍历T之右子树//
visit(T) //访问T所指结点//
从上述三个算法中看出,若抹去 visit(T)语句,则三个算法是一样的,可以推断,这三个算法的递归路线是一致的(走的线路是一样的,只是对结点的访问时间不同而已,前序是第一次碰到就访问,中序是第一次返回时访问,后序是第二次返回时访问。
遍历非递归算法
1.前序遍历二叉树的非递归算法
算法思路:设一存放结点指针的栈S。从根开始,每访问一结点后,按前序规则走左子树,但若该结点右子树存在,则右子指针进栈,以便以后能正确地遍历相应放回到该右子树上访问。
算法描述: void Preoder-1(BTptr T) //前序非递归遍历二叉树T//
BTptr pstacktype s
Clearstack(s)
push(s,T) //置栈S为空、根指针T进栈//
while (!Emptystack(s) )
p=pop(s) //出栈,栈顶=>P//
while (p)
visit (p) //访问p结点//
if (p&ndash>Rchild) push(s,p&ndash>Rchild) //右子存在时,进栈//
p=p&ndash>Lchild
//向左走//
说明:内部循环是从P结点出发一直走到最左,走的过程中保存了每一个右子树的地址,(因为右子树还没有被访问)而且是先进后出的,即先保存的比后保存的更先被用作返回地址,所以是用栈。外循环正好是当内部循环不下去的时候,退一栈的情形。即换成他的右子树
2.中序遍历二叉树的非递归算法
算法思路:同前序遍历,栈S存放结点指针。对每棵子树(开始是整棵二叉树),沿左找到该子树在中序下的第一结点(但寻找路径上的每个结点指针要进栈),访问之然后遍历该结点的右子树,又寻找该子树在中序下的第一结点,..….直到栈S空为止。
算法描述: void Inorder-1 (BTptr T) // 中序非递归遍历二叉树T//
BTptr p stacktype s
Clearstack(s)
push (s,T) //置栈S空、根指针进栈//
while (!Emptystack(s))
while ((p=Getstop (s))&& p) // 取栈顶且栈顶存在时//
push(s,p->lchild) //p之左子指针进栈//
p=pop(s) //去掉最后的空指针//
if (!Emptystack (s))
p=pop(s) //取当前访问结点的指针=>P//
visit(p) //访问P结点//
push(s,p-> Rchild) //遍历P之右子树//
说明:和前序不一样,这里的栈保存的是根结点的地址(因为中序遍历先访问左子树,而根结点没有被访问到。而前序遍历不一样,他一开始就访问根结点,所以他不保存根结点的地址而是保存右子树的地址,因为右子树还没有被访问。总之,用栈就是为了帮我们保存还没有被访问的地址,以便将来我们能找到返回的地址)
3.后序遍历二叉树的非递归算法
算法思路:后序非递归遍历较之前序、中序算法要复杂一些。原因是对一个结点是否能访问,要看它的左、右子树是否遍历完,所以每结点对应一个标志位–tag。tag=0,表示该结点暂不能访问tag=1,表示该结点可以访问。其实是区分这次返回是遍历完左子树返回的还是遍历完右子树返回的,如果是左子树返回那么就不能访问根结点,如果是右子树返回的就能访问根结点。
当搜索到某P结点时,先要遍历其左子树,因而将结点地址P 及tag=0进栈当P结点左子树遍历完之后,再遍历其右子树,又将地址P及tag=1进栈当P结点右子树遍历完后(tag=1),便可以对P结点进行访问。
栈元素类型: typedef struct
BTptr q // 存放结点地址 //
int tag //存放当前状态位//
stype
算法步骤如下:
①若二叉树T=&and(空树),返回,算法终止
②初始化:置栈S空,根指针T=>p
③反复以下过程,直到p=&and且栈S=&and时算法终止:
若p&ne&and,(p,o)进栈,p=p&ndash>Lchild (遍历左子树) ,……,直到 p=&and出栈, 栈顶=>(p, tag)若tag=0,(p,1)进栈,p=p&ndash>Rchild(遍历右子树),否则,访问p结点,并置p=&and。
算法描述:void postorder-1(BTptr T) //后序非递归遍历二叉树T//
int tag BTptr p stype sdata
Clearstack (s) // 置栈空 //
p=T
do
while (p)
sdata.q=p sdata.tag=0
push (s, sdata) // (p,0)进栈//
p=p&ndash>Lchild //遍历p之左子树//
sdata=pop(s) //退栈
p=sdata.q //取指针
tag=sdata.tag//状态位//
if (tag= =0)//从左子树返回时,根的tag=0
sdata. q=p
sdata.tag=1 //这时要进入根的右子树了,所以将根的tag=1,
//下次碰到根时就可以访问了
push(s,sdata) // (p,1) 进栈,根还得进一次栈
p=p&ndash>Rchild //遍历右子树//
else //tag=1,这时说明右子树访问完了后返回,所以这次要对根访问了
visit(p) //访问p结点//
p=NULL//要再退到上层,因为该棵树已经彻底访问完了
//之所以把p=null是不让他进入while
while ( p|| !Emptystack(s))
例题:假设二叉树采用链式存储结构进行存储,root^为根结点,p^为任一给定的结点,
写出求从根结点到p^之间路径的非递归算法。
[题目分析]采用后序非递归遍历二叉树,栈中保留从根结点到当前结点的路径上所有结点。
void PrintPath(BiTree bt,p) //打印从根结点bt到结点p之间路径上的所有结点
BiTree q=bt,s[] //s是元素为二叉树结点指针的栈,容量足够大
int top=0 tag[]//tag数组元素值为0或1,访问左、右子树标志,tag和s同步
if (q==p)
printf(q->data)
return
//根结点就是所找结点
while(q!=null || top>0)
while(q!=null) //左子女入栈,并置标记
if(q==p) //找到结点p,栈中元素均为结点p 的祖先
printf(“从根结点到p结点的路径为n”)
for(i=1i<=topi++)
printf(s[i]->data)
printf(q->data)
return
else
s[++top]=q
tag[top]=0
q=q–>lchild
//沿左分支向下
while(top>0 && tag[top]==1))
top--//本题不要求输出遍历序列,这里只退栈
if (top>0)
q=s[top]
q=q->rchild
tag[top]=1
//沿右分支向下
//while(q!=null || top>0)
//结束算法PrintPath
按层次遍历二叉树
算法描述:
void Layerorder(BTptr T) //对二叉树T按层次遍历//
BTpfr p qtype Q
if (T)
Clearqueue (Q) //置队Q空//
Enqueue (Q,T) //将根指针进队//
while (!Emptyqueue(Q) )
p=Dequeue(Q) //出队,队头元素Þp//
visit (p) //访问p结点//
if (p->Lchild) Enqneue (Q,p->Lchid) //左子指针进队//
if (p->Rchild) Enqneue (Q,p->Rchid) //右子指针进队//