昨天面试了美团,面试官要求写出二叉树的中序遍历,要求实现递归与非递归方式。在写非递归的时候,一个while循环中忘记控制空指针,╮(╯▽╰)╭当时脑子一片空白不在状态。回来后告诉自己,这类问题以后绝不会发生!下述6种实现方法希望大家都能理解,并快速能手写代码出来。(理解,懂得思路才是王道,因为面试的时候人的情绪相对来说是比较紧张,需要掌握得更熟悉)
先(前)序递归遍历
void preorderTraverse(BiTree T){
if(T){
visit(T);
preorderTraverse(T->lChild);
preorderTraverse(T->rChild);
}
}
中序递归遍历
void inOrderTraverse(BiTree T){
if(T){
inOrderTreverse(T->lChild);
visit(T);
inOrderTreverse(T->rChild);
}
}
后序递归遍历
void lastorderTraverse(BiTree T){
if(T){
preorderTraverse(T->lChild);
preorderTraverse(T->rChild);
visit(T);
}
}
void preorderTraverse(BiTree T){
InitStack(S);
Push(S,T);
while(!StackEmpty(S)){
while(GetTop(S,p) && p){
//先打印再添加左子树
visit(p);
Push(S,p->lChild);
}
//弹出空指针
Pop(S,p);
//向右走一步
if(!StackEmpty(S)){
Pop(S,p);
Push(S,p->rChild);
}
}
}
void inOrderTraverse(BiTree T){
InitStack(S);
Push(S,T);
while(!StackEmpty(S)){
//一直向左找,直到最后一个为空指针
while(GetTop(S,p) && p)
Push(S,p->lChild);
//弹出空指针
Pop(S,p);
//向右走一步
if(!StackEmpty(S)){
Pop(S,p);
visit(p);
Push(S,p->rChild);
}
}
}
后序非递归遍历
typedef struct{
Node *p; //二叉树结点
int isVisited; //1表示所指的有结点被访问过
}SNode
非递归后序
void lastorderTraverse(BiTree T){
InitStack(S);
p = T;
//一直往左边走
while(p){
Push(S,p,0); p = p ->lchild; //表示把当前根节点push进栈,同时isVisited值为0
}
while(!StackEmpty(S)){
GetTop(S,sNode);
//如果右子树已经访问过或者没有右子树
if(!sNOde->p->rchild || sNode.isVisited == 1){
Pop(S,p);
visit(p);
}else{
//此时应该从右子树开始一直往左下方走到尽头
sNode.isVisited = 1;
p = sNode->rChild;
while(p){
Push(S,p,0);
p = p->lChild;
}
}
}
}
下面是我的理解和记住实现的几个技巧:
- 递归实现中,先(前)/中/后序的visit函数的调用在两句递归调用语句的前/中/后位置。
- 非递归实现中,先(前)和中序都是通过先把根压进栈,然后一直向左走走到尽头,弹出空指针后向右走一步,循环操作,不同在于:先(前)序遍历打印的时候是在添加左子树之前(因为根要先打印嘛),而中序是在添加右子树之前(因为根要比右子树先打印)。
- 非递归实现中,打印的条件为当前没有右子树或者右子树已经访问过;需要借助一个结构体来记录右子树是否被访问过;同样需要先把根压栈再向左走到尽头;假设有一棵树(ABC),A为根,B为左子树,C为右子树,那么利用打印条件和压栈顺序,A其实不可能优先于BC打印的,而怎么控制B先于C打印呢?就是在每次不满足打印条件的时候,就拿到其右子树后一直往左下方走,把结点都push进栈就可以。