要谈单栈实现二叉树后序遍历,就得先说说二叉树中序非递归遍历
基本思路是
- 设置一个指针current指向二叉树根
- 设置一个栈
- 让current入栈并且一直往左走,走一步,只要不空就入栈这个p
- 当不能往左走时就弹出栈顶,转向右子树进行中序遍历(就是对右子树执行上面的流程,其实就是把一个递归过程直接用循环表示出来)
因此我们有如下代码
void inorder(BiTree *T, void (*visit)(const BiNode *p))
{
BiNode *stack[MAX_SIZE_TRAVERSE_STACK];
int top = -1;
BiNode *current = T->root;
while (top > -1 || current) //只要栈不空或者这个current不空那就代表遍历没执行完
{
while (current) //就是上面说的3. (一直往左走)
{
stack[++top] = current;
current = current->lchild;
}
current = stack[top--]; //出栈访问之
visit(current);
current = current->rchild; //转向到右子树
}
}
然后考虑后序遍历。这个其实同中序遍历有相似之处,因为二者都是先访问左子树,故而在单栈实现上必然都是要先一路向左入栈的
但是后序遍历在结束完一次一路向左的while循环之后并不能直接将栈顶出栈,这时如果有右子树得先让右子树入栈,完成右子树遍历后最后访问这个节点
我们注意到在后序遍历中,若某个节点没有右子树,那么从这个局部来看这里产生的序列就和中序一致(简单理解一下就行:中序LNR,后序LRN,拿掉R都成了LN)
而在后序遍历中某个节点是有右子树的,那么我们可以特殊处理一下,记录下右子树访问情况,当访问过右子树时这个节点才能出栈访问之。
另外,在后序遍历中某个节点是有右子树的,那么这个节点在后序遍历中的直接前驱是右子树根节点,于是我们就需要判断这个节点的右孩子(即当前节点右子树根节点)是否被访问,如果已经访问过了,那么这个节点(事实上这个当前节点也可以说是个某个子树的根)就可以出栈访问了,因为此时右子树已经访问完毕。
因此我们有如下代码(没记错的话重庆大学计算机考研前两年就考了这个,不过现在重大改考408了)
void posorder(BiTree *T, void (*visit)(const BiNode *p))
{
BiNode *stack[MAX_SIZE_TRAVERSE_STACK];
int top = -1;
BiNode *current = T->root;
BiNode *last_visited = NULL;
while (top > -1 || current) //类似于中序遍历的循环方式
{
while (current) //初始按中序方式一路向左入栈
{
stack[++top] = current;
current = current->lchild;
}
current = stack[top]; //不出栈可能未到到访问时机
if (current->rchild && last_visited != current->rchild) //有右子树且未被访问转向右子树
current = current->rchild;
else
{
visit(current);
last_visited = current; // 后序遍历下某个节点的前驱在当前节点的右子树存在时必然为右孩子因而有if中判断last_visited
--top;
current = NULL; // 防止这个刚被访问的current进入开头while循环重复入栈被访问
}
}
}
浙公网安备 33010602011771号