二叉树遍历的非递归算法

所谓二叉树的遍历就是按照某种次序访问二叉树中每个节点,而且每个节点仅访问一次的过程。以L,N,R分别表示遍历左子树、访问根节点和遍历右子树,则可能有NLR、LNR、LRN、NRL、RNL、RLN等六种遍历方式。若限定先左后右,则只有前三种遍历,分别称为先序遍历、中序遍历、后序遍历,另外还有一种层序的遍历方式。

由于遍历的递归算法比较简单,这里只介绍遍历的非递归算法。这里给二叉链存储结构中节点类型给出如下定义便于后面代码书写,定义如下:

typedef struct node

{

ElementType data;//数据元素

struct node *lchild;//指向左孩子节点

struct node *rchild;//指向右孩子节点

} BTNode;

下面以如图所示的二叉树对应的二叉链存储结构为例讲解:

二叉树遍历的非递归算法_第1张图片















1.先序遍历

a.访问根节点

b.先序遍历左子树。

c.先序遍历右子树。

由先序遍历过程可知,先访问根节点,再访问左子树,最后访问右子树,因此,先将根节点进栈,在栈不空时循环:出栈p,访问*p节点,将其右孩子节点进栈,再将左孩子节点进栈。(简述:先根进栈 出栈 再右子树进栈 左子树进栈

先序遍历的非递归算法如下:

void  PreOrder(BTNode *p)//先序遍历的非递归算法

{

BTNode *St[MaxSize],*p;//顺序栈St中保存的是节点指针,而不是节点值

int top=-1;

if(b!=NULL)

{

top++;//根节点进栈

St[top]=b;

while(top>-1)//栈不为空时循环

{

p=St[top];//退栈并访问该节点

top--;

printf("%c",p->data);

if(p->rchild!=NULL)//有左孩子节点时将其进栈

{

top++;

St[top]=p->rchild;

}

if(p->rchild!=NULL)//有右孩子节点时将其进栈

{

top++;

St[top]=p->lchild;

}

}

printf("\n");

}

}

先序遍历后的结果为:A B  D G C E F

2.中序遍历

a.中序遍历左子树

b.访问根节点

c.中序遍历右子树

由中序遍历的过程可知,采用一个栈保存需要返回的节点指针。先扫描(并非访问)根节点的所有左节点并将他们一一进栈。然后出栈一个节点*p,显然*p节点没有左孩子子节点或者左孩子节点并访问过(表明该节点的左子树均已访问)则访问它。然后扫描该节点的右孩子节点,将其进栈,再扫描该右孩子节点的所有左节点并一一进栈,如此这样,直到栈空为止。( 简述:先所有的左节点进栈,出栈一个节点, 右节点进栈

对应的非递归算法如下:

中序遍历的非递归算法

void InOrder(BTNode *b)

//中序遍历的非递归算法

{

BTNode *St[MaxSize],*p;//顺序栈St中保存的是节点指针

int top=-1;

if(b!=NULL)

{

p=b;

while(top>-1 || p!=NULL)//栈不为空或p不空时循环

{

while(p!=NULL)//扫描*p的所有左节点并进栈

{

top++;

St[top]=p;

p=p->lchild;

}

if(top>-1)

{

p=St[top];//出栈*p节点,它没有右孩子或右子树已访问

top--;

printf("%c",p->data);//访问之

p=p->rchild;//扫描*p的右孩子节点

}

}

printf("\n");

}

中序遍历后的结果为:D G B A E C F

3.后续遍历

后续遍历二叉树过程如下:

a.后序遍历左子树

b.后续遍历右子树

c.访问根节点

由后序遍历的过程可知,采用一个栈保存需要返回的节点指针,先扫描根节点的所有左节点并一一进栈,出栈一个节点*p,即当前节点,然后扫描该节点的右孩子节点并进栈,再扫描该右孩子节点的所有左节点并进栈。当一个节点的左、右孩子节点均访问后再访问该节点,如此这样,直到栈空为止。

其中的难点是如何判断一个节点*p的右孩子节点已访问过,为此用q保存右子树中刚刚访问过的节点(初值为NULL),若p->rchild==q成立(在后序序列中,*p的右孩子节点*q一定刚好在*p之前访问),说明*p节点的左、右子树均已访问,现在应访问*p节点。(简述:先将左节点进栈,出栈一个节点,右孩子进栈

从上述过程可知,栈中保存的是当前节点*p的所有祖先节点(均未访问过)。对应的非递归算法如下:

void PostOrder(BTNode *b)//后序遍历的非递归算法

{

BTNode *St[MaxSIze];//顺序栈St中保存的是节点指针

BTNode *p=b,*q;

int flag,top=-1;//栈指针置初值

if(b!=NULL)

{

do

{

while(p!=NULL)//将*p的所有左节点进栈

{

top++;

St[top]=p;

p=p->lchild;

}

q=NULL;//q指向栈顶节点的前一个已访问的节点

flag=1;//设置flag=1表示处理栈顶节点

while(top!=-1&&flag==1)

{

p=St[top];//取出当前的栈顶元素

if(p->rchild==q)//右孩子不存或右孩子已被访问,访问之

{

printf("%c",p->data);//访问*p节点

top--;

q=p;//让q指向被刚被访问的节点

}

else

{

p=p->rchild;//p指向右孩子节点

flag=0;//设置flag=0表示栈顶处理完毕

}

}

}while(top!=-1);//栈不为空时循环

printf("\n");

}

}

中序遍历后的结果为:DGBAECF

注意:当访问一个节点*p时,栈中节点恰好是*q节点的所有祖先。从栈底到栈顶节点再加上*q节点,刚好构成从根节点到*p节点的一条路径。在很多算法设计中都利用这一特性求解,如求从根节点到某节点的路径算法、求两个节点的最近公共祖先算法等都可以用这个思想来实现。

4.层次遍历

层次遍历的过程如下:

a.访问根节点(第一层)。

b.从左到右访问第二层的所有节点。

c.从左到右访问第三层的所有节点,…,第h层的所有节点

对应的层次遍历算法如下:

void LevelOrder(BTNode *b)//层序遍历的非递归算法

{

BTNode *p;

BTNode *qu[MaxSize]; //定义循环队列,存放节点指针

int front,rear;

//定义队头和队尾指针

front=rear=0;//置队列为空队列

rear++;

qu[rear]=b;//根节点指针入队

while(front!=rear)

{

front=(front+1)%MaxSize;//队头出队

p=qu[front];//访问节点

printf("%c",p->data);

if(p->lchild!=NULL)//有左孩子时将其进队

{

rear=(rear+1)%MaxSize;

qu[rear]=p->lchild;

}

if(p->rchild!=NULL)//有右孩子时将其进队

{

rear=(rear+1)%MaxSize;

qu[rear]=p->rchild;

}

}

}

层序遍历的结果为:ABCDEFG







你可能感兴趣的:(数据结构,二叉树遍历,非递归遍历算法)