在此之前,我们已经学习了中序遍历二叉树的递归算法,相信大家已经将其牢牢掌握了.
除了使用递归思想作为求解问题的钥匙,还可以借助栈来以非递归方式实现该问题的求解.
首先,我们要讨论存储二叉树结点信息的栈的形态:是只存储二叉树结点的指针,还是要完整存储二叉树结点的信息(LChild指针+数据域data+RChild指针). 在这里先和大家说明,经过程序执行验证,(※)必须将二叉树结点的信息完整入栈,而不能像层次遍历时那样只把结点指针存入队列.
接下来我们来看一下二叉树结点栈的定义形式.
struct Stack//栈的定义
{
int base;//栈底指针
int top;//栈顶指针
BiTreeNode BTNS[MAXSIZE];//二叉树结点数组
int stackSize;//栈可用的最大容量
};
[注]:BTNS[]数组为二叉树结点数组,该数组即为顺序栈的空间.
接着,我们需要初始化二叉树结点栈.
void InitStack(Stack* &S)//初始化栈
{
S=(Stack*)malloc(sizeof(Stack));
S->top=S->base=0;
S->stackSize=MAXSIZE;
}
为了算法的严谨,我们还需要在栈空和栈满时做出反应,以避免越界访问数组的问题发生.
bool StackEmpty(Stack* &S)//判断栈是否为空
{
if(S->base==S->top)
{
return true;
}
else
{
return false;
}
}
bool StackFull(Stack* &S)//判断栈是否已满
{
if(S->top - S->base == S->stackSize)
{
//栈已满
return true;
}
else
{
//栈不满
return false;
}
}
最后,还需要二叉树结点进出栈的操作.
void Push(Stack* &S, BiTreeNode* &T)//元素入栈
{
if(StackFull(S)==true)
{
//如果栈已满, 则直接返回
return ;
}
//把结点信息复制到栈中
S->BTNS[S->top].data=T->data;
S->BTNS[S->top].LChild=T->LChild;
S->BTNS[S->top].RChild=T->RChild;
S->top++;
//元素入栈 -> 将结点信息复制到栈的某个位置中(※)
//一开始我选择将结点指针入栈, 但如此做会导致程序报错
//一直找不出问题, 后来才发现这样做是错误的
}
BiTreeNode* Pop(Stack* &S)//元素出栈
{
if(StackEmpty(S)==true)
{
//栈已空
return NULL;
}
S->top--;//栈顶"指针"下移
return &(S->BTNS[S->top]);
}
[注]:如果有朋友对上面栈的操作理解不透彻,这里先将栈看作一个增广的数据类型即可:即直接使用其操作函数,不必在意其实现细节. 我会在六月中旬到七月上旬间和大家分享有关线性结构的相关操作算法.
有了辅助栈的帮忙,我们距离实现算法又近了一步. 接下来我们需要分析中序遍历的非递归过程.
我们在中序遍历一棵二叉树时,遵循的规则为"左根右":据此遍历规则,可以得到下面的思维过程:
①p指向树根结点;
②如果p非空,那么跳转至③;如果p为空,那么跳转至④;
③p=p->LChild(p指向原来指向结点的左孩子结点),跳转至②;
④栈顶元素出栈,并使指向出栈元素,p=q->RChild(p指向出栈元素的右孩子结点),跳转至②;
依据上面的思维过程,我们不难看出,该算法无法结束:我们还必须考虑算法的结束条件. 当p=NULL时,如果栈也空了,那么说明所有的结点都被访问过了,此时算法结束.
据此分析,将上面的思维过程改写为下面的形式:
①p指向树根结点;
②如果p非空,那么跳转至③;如果p为空且辅助栈不空,那么跳转至④;如果p为空且辅助栈也为空,那么跳转至⑤;
③p=p->LChild(p指向原来指向结点的左孩子结点),跳转至②;
④栈顶元素出栈,并使指向出栈元素,p=q->RChild(p指向出栈元素的右孩子结点),跳转至②;
⑤结束.
由此思维过程,得到下面的算法实现代码.
void InOrderTraverse(Stack* &S, BiTreeNode* &T)//中序遍历二叉树的非递归算法(※)
{
InitStack(S);//初始化栈
BiTreeNode* p=T;
BiTreeNode* q;
while(p||!StackEmpty(S))
{
//当p=NULL且栈空时, 循环结束
if(p)
{
//根结点入栈
Push(S,p);
//继续往左下方走
p=p->LChild;
}
else
{
//左下方没路了, 访问栈顶元素,并将其出栈
q=Pop(S);//出栈元素指针保存在q中
putchar(q->data);
cout<<" ";
//之后再往出栈元素的右下方走
p=q->RChild;
}
}
}
大家应着重掌握这里的中序遍历非递归算法,这对培养算法思维有着极大的帮助.
还有一点值得说明,那就是当你把栈和队列看作一种增广的数据类型(使用栈和队列的相关操作时,忽略其内部实现细节)时,那么你的数据结构能力已经成功地从"菜鸟"级别迈入"初学者"级别了.
[完整源程序代码]:
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#define MAXSIZE 20
using namespace std;
struct BiTreeNode//二叉树结点定义
{
BiTreeNode* LChild;//左孩子指针域
int data;
BiTreeNode* RChild;//右孩子指针域
};
struct Stack//栈的定义
{
int base;//栈底指针
int top;//栈顶指针
BiTreeNode BTNS[MAXSIZE];//二叉树结点数组
int stackSize;//栈可用的最大容量
};
void InitStack(Stack* &S)//初始化栈
{
S=(Stack*)malloc(sizeof(Stack));
S->top=S->base=0;
S->stackSize=MAXSIZE;
}
bool StackEmpty(Stack* &S)//判断栈是否为空
{
if(S->base==S->top)
{
return true;
}
else
{
return false;
}
}
bool StackFull(Stack* &S)//判断栈是否已满
{
if(S->top - S->base == S->stackSize)
{
//栈已满
return true;
}
else
{
//栈不满
return false;
}
}
void Push(Stack* &S, BiTreeNode* &T)//元素入栈
{
if(StackFull(S)==true)
{
//如果栈已满, 则直接返回
return ;
}
//把结点信息复制到栈中
S->BTNS[S->top].data=T->data;
S->BTNS[S->top].LChild=T->LChild;
S->BTNS[S->top].RChild=T->RChild;
S->top++;
//元素入栈 -> 将结点信息复制到栈的某个位置中(※)
//一开始我选择将结点指针入栈, 但如此做会导致程序报错
//一直找不出问题, 后来才发现这样做是错误的
}
BiTreeNode* Pop(Stack* &S)//元素出栈
{
if(StackEmpty(S)==true)
{
//栈已空
return NULL;
}
S->top--;//栈顶"指针"下移
return &(S->BTNS[S->top]);
}
//
void CreateBiTree(BiTreeNode* &T)//以先序序列创建二叉树
{
char ch;
cin>>ch;
if(ch!='#')
{
T=(BiTreeNode*)malloc(sizeof(BiTreeNode));
T->data=ch;
CreateBiTree(T->LChild);
CreateBiTree(T->RChild);
}
else
{
T=NULL;
}
}
void InOrderTraverse(Stack* &S, BiTreeNode* &T)//中序遍历二叉树的非递归算法(※)
{
InitStack(S);//初始化栈
BiTreeNode* p=T;
BiTreeNode* q;
while(p||!StackEmpty(S))
{
if(p)
{
Push(S,p);
p=p->LChild;
}
else
{
q=Pop(S);//出栈元素指针保存在q中
putchar(q->data);
cout<<" ";
p=q->RChild;
}
}
}
int main()
{
Stack* S;
BiTreeNode* T;
CreateBiTree(T);
InOrderTraverse(S,T);
return 0;
}