//二叉树的遍历算法之非递归版本(摘自天勤考研之数据结构第六章)
/************************************************************************/
/*
前面介绍的二叉树深度右边遍历算法都是通过递归函数实现的,这时很低效的。原因在于递归算法使用了系统提供的栈。并且在这个
系栈中还做了诸如保护现场和恢复现场等复杂的操作,才使得遍历可以用非常简洁的代码实现。下面介绍了两种算法:二叉树深度优
先遍历遍历算法的非递归实现和线索二叉树。
所谓的二叉树非递归算法并非不使用栈。非递归算法只不过是不使用系统提供的栈,而是使用用户自定义的栈来代替系统栈,这就是
非递归算法实现的核心。采非递归版本的遍历算法可以得到不小的效率提升。
而二叉树的线索化,就更不需要栈来辅助就能完成遍历操作,更进一步的提升了效率。而且二叉树的线索化应用还不仅仅如此。它
还可以用于判断一棵树是否为另一棵树的子树。就提做法就是将待判定的两颗树进行线索化得到两个线索化后的序列,然后使用KMP
算法进行串的模式匹配。若一个串是另一个串的淄川,则对应的树就是另一颗树的子树。若不是子串,则不为子树
二叉树的先序遍历非递归版本的思想如下:
设T是指向二叉树根结点的指针变量,
1、用户自定义一个栈
2、若二叉树为空则返回,否则令P=T
1)访问P所指向的结点
2)令Q=P->rchild,若Q!=NULL,则Q进栈(因为栈的先进后出的特性,而树的遍历过程中左子树访问始终是在右子树的前面,所以是右子树先进栈,左子树后进栈才
能保证对左子树的访问先于右子树)
3)给P赋值为:P=P->lchild,若P为空转1),若P不为空,转4)
4)退栈到P,转1)直到栈为空
上代码:
*/
/************************************************************************/
void PreorderNonrecursion(BTNode* bt) //二叉树中序遍历非递归版本
{
if (NULL != bt)
{
stack<BTNode* > istack; //STL中的栈
int top = 1;
BTNode* p = NULL;
istack.push(bt); //根结点入栈
while (!(istack.empty())) //栈空退出循环,遍历结束
{
p = istack.top();
istack.pop(); //栈顶元素出栈并保存到指针p中
visit(p); //visit为访问p的函数
if (p->rchild != NULL) //栈顶元素的右孩子存在,则右孩子入栈
{
istack.push(p->rchild);
}
if (p->lchild != NULL) //栈顶元素的左孩子存在,则左孩子进栈
{
istack.push(p->lchild);
}
}
}
}
/************************************************************************/
/*
二叉树中序遍历非递归版本
其过程如下:
1、开始时根结点入栈
2、循环指向下面的操作:如果栈顶结点的左孩子存在,则左孩子入栈;如果栈顶结点的左孩子不存在则栈顶结点出栈
并进行输出或访问操作,然后再检查其右孩子结点是否存在,如果存在则右孩子结点入栈
3、当栈空时算法结束
注意二叉树中序遍历的非递归版本中执行过程变为了:根结点入栈、左孩子入栈(如果左孩子结点存在)、左孩子结点出栈、访问左孩子结点、
访问根结点、右孩子入栈(如果右孩子存在)、右孩子出栈、访问右孩子。和先序遍历过程中的入栈出栈顺序不一样了
*/
/************************************************************************/
void InorderNonrecursion(BTNode* bt)
{
if (bt != NULL)
{
stack<BTNode* > stack2;
/*int top = -1;*/
BTNode* P = NULL;
P = bt;
/*
下面这个循环完成中序遍历,注意此过程中会出现栈空状态但这时候遍历还没有结束,因为根结点的右子树还没有遍历,此时P非空,应该是根据这一点
控制循环的继续还是结束
*/
while (!(stack2.empty()) || P != NULL)
{
while (P != NULL) //左孩子存在则左孩子入栈
{
stack2.push(P);
P = P->lchild;
}
if (!(stack2.empty())) //在栈不为空的情况下出栈并访问出栈结点
{
P = stack2.top();
stack2.pop(); //弹出栈顶节点
visit(P); //Visit()为访问p的函数
P = P->rchild;
}
}
}
}
//二叉树后序遍历算法非递归版本(这个比较难以理解)
/************************************************************************/
/*
通过对比二叉树的后序遍历和现需遍历不难发现:二叉树的后续遍历的逆序和先序遍历有一定的关系,具体就是:逆后序遍历序列只不过是先序遍历过程中对左右子树
遍历顺序(根结点在先序遍历中的位置不动,根结点左边的就是左子树的先序序列,根结点右边的就是右子树的先序序列)进行交换的结果。
因此只需要将前边讲到的非递归先序遍历算法中左右子树的遍历顺序进行交换就可以得到逆后序序列。然后将逆后序序列逆序就得到了后序遍历序列。为此我们需要准
备两个栈:一个栈stack1用于辅助做逆后序序列(将先序遍历的左、右子树遍历序列进行交换的遍历方式称为逆后序遍历)并将遍历结果压入另一个栈stack2中,然后
将stack2中的全部元素出栈得到的序列后序遍历序列
上代码:
*/
/************************************************************************/
void PostOrderNonrecursion(BTNode* bt)
{
if (bt != NULL)
{
//定义两个栈
stack <BTNode* > stack1, stack2;
BTNode* p = NULL;
stack1.push(bt);
while (!(stack1.empty()))
{
p = stack1.top();
stack1.pop();
stack2.push(p); //注意此处和先序遍历的区别
//注意下面这两个if语句和先序遍历的区别:左右孩子入栈顺序相反
if (p->lchild != NULL)
stack1.push(p->lchild);
if (p->rchild != NULL)
stack1.push(p->rchild);
}
while (!(stack2.empty()))
{
p = stack2.top(); //出栈顺序即为后序遍历序列
stack2.pop();
visit(p); //visit()为访问结点p的函数
}
}
}