网上流传了很多二叉树的非递归写法,对于前序和中序,比较容易理解,对于后序,很多讲的不是非常明白。参考了很多资料,本人自己写了
和传统前序,中序非递归方法类似的写法。读本文前,最好先看过文末的参考文献。
首先定义基本的二叉树
struct TreeNode{
char c;
TreeNode *left;
TreeNode *right;
};
typedef struct TreeNode * BinTree;
typedef struct TreeNode _TreeNode;
1、前序和中序。
非递归思路如下,
a. 遇到一个节点,访问它,然后把它压栈,并去遍历它的左子树;
b. 当左子树遍历结束后,从栈顶弹出该节点并将其指向右儿子,继续a步骤;
c. 当所有节点访问完即最后访问的树节点为空且栈空时,停止。
void PreOrder(BinTree T){
stack<BinTree> s;
BinTree P = T;
while(P || !s.empty()){
while(P){
cout<<P->c;
s.push(P);
P = P->left;
}
if(!s.empty()){
P = s.top();
s.pop();
P = P->right;
}
}
}
2、后序
其实,二叉树遍历的非递归写法,本质上仍然是根据递归写法推导出来的。按照左右根的顺序,我们自己模拟一下递归的过程,不难得出下面的过程。算法思路,(1)P=P->left,一直调用左子树,直到最底下一层调用左子树为空。此时,令P=stack.top(),即循环中最后一个不为空的左子树。根据P->right的情况,分为下面两种跳转:(2)P->right不为空,令P=P->right,跳到(1)。看到这里,是不是跟传统的前序和后序非递归思路很像?(3)P->right为空。由于(2)(3)是(1)的两种情况的不同跳转,到了这里就说明P的左右子树均为空。因此,访问P。接下来,我们需要一个prev前驱。令prev=P,然后将P出栈,重新将P只向栈顶。若P的右儿子是prev,则说明,右儿子访问完了,访问P。这里我们需要一个循环,一只判断是否需要访问。若P的左儿子是prev,则我们需要专向它的右儿子,重新从(1)开始下一轮循环。下面贴出代码
void PostOrder2(BinTree T){
stack<BinTree> s;
BinTree P = T,prev;
while(P || !s.empty()){
while(P){ //循环访问左子树,直到为空
s.push(P);
P = P->left;
}
if(!s.empty()){
P = s.top(); //P重新只向栈中最后一个非空元素。
if(P->right){ //右儿子非空,转向右儿子。重新开始下一轮处理。类似前序的的非递归处理。
P = P->right;
continue;
}
//若P右儿子为空,或者右儿子前面已经访问了,继续访问P
while(P->right == NULL || P->right == prev){
cout<<P->c<<' ';
prev = P;
s.pop();
if (s.empty())
return;
P = s.top();
}
//若P的左儿子前面已经访问了,转向右儿子。重新开始下一轮处理。类似前序的的非递归处理。
if(P->left == prev)
P = P->right;
}
}
}
完整测试代码分享:链接:http://pan.baidu.com/s/1migbYWS 密码:h7x4
参考资料:
1、严蔚敏,《数据结构》,清华大学出版社
2、llhthinker。 《二叉树的遍历:先序中序后序遍历的递归与非递归实现及层序遍历》。http://www.cnblogs.com/llhthinker/p/4747962.html。