二叉树前序、后序和后序遍历(非递归实现)


(1)前序


我们知道,前序遍历的顺序是根左右,当根节点不为空时,该节点才可以被打印。目前书上常见对树的遍历都是采用递归的方法实现的,我们知道递归必然会产生中断,也就是有现场信息的保存,如果要实现非递归,那么我们必须自己要有一个栈,用来保存现场信息。


我先给出实现的伪代码,稍后我将解释为什么需要这么做,为何要用到这些条件。

伪代码如下:


1 void PreOrderTraverse(BinaryTree root)

2 {

3 BinaryTree cur = root;

4 stack<BinaryTree> s;

5 while(null != cur || !s.empty())

6 {

7 while(null != cur)

8 {

9 print cur.data;

10 s.push(cur);

11 cur = cur.left;

12 }

13 if(!s.empty()) {

14 cur = s.top();

15 s.pop();

16 cur = cur.right;

17 }

18 } //loop end!

19}


第3行的cur就相当于递归中的现场信息,而第4行声明到的栈则用来保存它。

第5行的循环条件暂时不用关注,我们看第7行到第12行的代码,它主要做的是,如果当前节点不为空,则打印,同时将该结点的信息保存到用户栈中,接着把当前节点改变成它到的左子。当左子为空时,循环结束。

接着再看第13行到第16行的代码,它做的是取栈顶的现场信息(也就是最后一次保存的现场信息),然后改变当前结点为它的右子。


最后我们关注这个大循环结束的条件。当第16行执行结束,cur为右结点,可能存在两种情形:


① 右子为空,那么我们从用户栈恢复保存的现场信息。

② 右子不为空,那么整个逻辑不变,按照之前的方法进行处理。


所以我们得出整个循环继续得以执行的条件是结点不为空或者用户栈不空,满足二者其一即可。


(2)后序


后序遍历的顺序是左右根,我们要保证根节点在左孩子和右孩子访问之后才能访问。


首先我们来考虑一个结点P能被访问的条件:


① P的左孩子和右孩子都为空,则该节点可以被直接访问;

② P有左孩子或右孩子,但左孩子和右孩子都已经被访问过,那么结点P也可以直接访问。


如果不是上面两种条件,那我们将P的右孩子和左孩子依次入栈,这样就可以保证每次取栈顶元素的时候,左孩子在右孩子的前面被访问,左孩子和右孩子都在根节点的前面被访问。


实现的伪代码如下:

void PostOrderTraverse(BinaryTree root)

{

if(null == root)

return;

stack<BinaryTree> s;

s.push(root);

BinaryTree pre = null;

BinaryTree cur;

while(!s.empty())

{

cur = s.top();

if(null == cur.lchild) && null == cur.rchild

|| (null != pre) && (pre == cur.lchild || pre == cur.rchild)) {

print cur.data;

s.pop();

pre = cur;

}

else {

if(null != cur.rchild) {

s.push(cur.rchild);

}

if(null != cur.lchild) {

s.push(cur.lchild);

}

}

} //loop end!

}