我们都知道,对二叉树进行递归遍历非常简单,但递归算法需要额外的栈机制来存储每次递归的值。既然递归算法内部使用栈实现的,那么我们也可以借助于栈来实现二叉树的非递归遍历。下面我们将讲解利用非递归实现二叉树的前序、中序和后序遍历。
1、非递归二叉树前序遍历:
我们知道,二叉树的前序遍历对节点的访问顺序是根节点、左子节点然后右自节点。根据其访问顺序我们可以很容易用栈来实现。具体实现思路如下:
1、遍历根节点的左子树,将每个节点的左子节点存入栈中,并在访问遍历的节点。
2、当遇到左子节点为空时,从栈中取出该节点的父节点,遍历其父节点的右子节点。
3、若该节点无右子节点,则将其从栈中删除,取该节点的父节点,并访问其右节点。
4、若该节点有右子节点,则将其右子节点当做根节点,重复1.
具体实现代码如下:
void preorderWithOutRecursion(BinaryTreeNode *proot)
{
BinaryTreeNode *p = proot;
BinaryTreeNode *tmp = NULL;
stack<BinaryTreeNode *> mystack;
if(proot == NULL)
{
return;
}
while(p)
{
mystack.push(p);
cout << p->val << '\t'; //访问遍历的节点
p = p->pleft;
}
while(!mystack.empty())
{
p = mystack.top();
mystack.pop();
p = p->pright;
while(p)
{
mystack.push(p); //当右节点存在时,重复前面步骤
cout << p->val << '\t';
p = p->pleft;
}
}
}
二叉树的中序遍历基本思想是先遍历根节点左子树,然后访问根节点,最后遍历根节点的右子树。同理,我们根据上面前序遍历的经验可以完成中序非递归遍历:
1、遍历根节点的左子树,将其存入栈中。
2、当遍历完左子树后,访问根节点。
3、遍历根节点右子树。
具体代码如下:
void inorderWithOutRecursion(BinaryTreeNode *proot)
{
BinaryTreeNode *p = proot;
if(p == NULL)
{
return;
}
stack<BinaryTreeNode *> mystack;
while(p)
{
mystack.push(p);<span style="white-space:pre"> </span> //遍历左子树
p = p->pleft;
}
while(!mystack.empty())
{
p = mystack.top();
mystack.pop();
cout << p->val << '\t';<span style="white-space:pre"> </span> //访问根节点
p = p->pright;<span style="white-space:pre"> </span> //遍历右子树
while(p)
{
mystack.push(p);
p = p->pleft;
}
}
}
3、非递归二叉树后续遍历
二叉树的后续遍历时先遍历左子树,然后遍历右子树,最后访问根节点。那么利用非递归方式对二叉树进行遍历的难点在于如何确定我们是否遍历过根节点的右子树。我们从二叉树的前序中序遍历的代码中可以看到,当我们将节点从栈中取出后直接将其pop()掉,因为前序遍历中我们存入该节点时已经对其进行访问,而中序遍历中我们取出该节点时对其访问。但由于后续遍历时最后访问跟节点的,所以我们只有遍历完根节点的左右子树后才能将其从栈中pop(),也就是我们需要一个标志来标志是否访问过该节点的右子树(无论是前序、中序还是后续遍历,我们的while循环都是遍历左子树,所以当节点存入栈中时已经遍历过其左子树)。这样我们就需要引入一个新的结构。
1、遍历根节点的左子树,将其存入栈中。
2、判断该节点是否遍历过右子树,若没有遍历过则遍历其右子树
3、若遍历过右子树,则访问该节点,并将其从栈中删除
具体代码如下:
struct newNode<span style="white-space:pre"> </span> //新节点,两个public数据成员,一个节点指针,另一个bool值判断其右节点是否被遍历过
{
BinaryTreeNode *pNode;
bool tag;
newNode(){<span style="white-space:pre"> </span> //默认构造函数
}
newNode(BinaryTreeNode *p, bool Tag):pNode(p), tag(Tag){ //带参数构造函数
}
};
void postorderWithOutRecursion(BinaryTreeNode *proot)
{
BinaryTreeNode *p = proot;
if(p == NULL)
{
return;
}
newNode *tmp;
stack<newNode*> mystack;
while(p) //保存左节点
{
mystack.push(new newNode(p, false)); //遍历左子树,里面存的是新节点,初始tag值为false
p = p->pleft;
}
while(!mystack.empty())
{
tmp = mystack.top();
if(tmp->pNode->pright && !tmp->tag) //查看该节点是否存在右节点及该节点是否被遍历过
{
tmp->tag = true; //遍历其右子树,并将tag设为true
p = tmp->pNode->pright; //遍历右子树
while(p)
{
mystack.push(new newNode(p, false));
p = p->pleft;
}
}
else //如果该节点不存在右子树或者该节点右子树已经访问过则访问该节点
{
cout << tmp->pNode->val << '\t';
mystack.pop();
}
}
}
对二叉树的非递归遍历主要是借助于栈来实现的,无论是前序中序还是后序遍历都是先遍历根节点的左子树,然后遍历右子树。唯一不同的是访问根节点的时机。前序遍历是先访问根节点再遍历子树,中序遍历是先遍历左子树再访问根节点,后序遍历是最后访问跟节点。相比于前序和中序遍历,后序遍历稍微麻烦点,因为我们需要设置标志位来标志是否遍历过其右节点。