二叉树的非递归遍历--百度面试有感
上周百度内推招聘,无意间发了个简历,也没抱太大希望,三天后,收到面试通知。面试官是一个年级不大的人,戴着一副黑框眼镜,感觉还比较随和。废话不多说,面试就考到了二叉树的遍历,我开始心想,这不太easy了吧,写完后,懵逼了,面试官批评说递归遍历企业实际业务中是不会用的,你把二叉树后续遍历非递归形式写出。。。what!!!!!最难的那个!好吧,硬着头皮写了二十多分钟,还是没写出来,瞬间感觉自己low爆了。若要虐人,先被人虐,虽然没答上来,但还是提醒我平时要注意基础算法的掌握。后来回到实验室自己一个人苦思冥想,终于想出来了,把代码分享一下,欢迎各位大神斧正。
后序遍历一个二叉树,递归比较简单,在此就不再赘述了。值得提醒的是,使用递归方法其本质是调用系统栈来保存断点,而系统栈区是有限的,对于深度比较小的树尚能承受,一旦遇到深度比较大的树就很容易出现系统栈溢出的情况,所以在公司业务场景中很少使用,或者不使用,仅存在理论上的意义。既然不能使用递归方式,但还是要深度优先遍历,每次项下一层结点的访问都需要保存上一层的结点,很容易想到使用栈来保存。二叉树非递归遍历本质就是让我们显示定义栈,然后维护栈中结点保证二叉树的后序遍历次序。
但为什么说在二叉树的三种遍历方式中,非递归后序遍历难度最大呢? 这是因为先序遍历或者中序遍历在用栈辅助的非递归算法中,每个子树的根总是被访问一次,而再后序遍历中会访问过程中会遇到两次:
- 为了先访问左子树而经过根结点,此时不能visit根结点;
- 为了先访问右子树而经过根结点,此时可以进行visit操作;
为了标识根结点是第一次访问还是第二次访问,故定义了一个CntTreeNode类来“包装”原树中的结点,并增加一个标识为isFirstVisit来表示当前结点是不是第二次被访问。
二叉树的结点定义为Java类TreeNode,详细定义如下:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x;}
}
二叉树后续遍历非递归方式的详细代码如下:
public class PostOrderTravel {
//声明一个内部类,增加访问标识
class CntTreeNode{
TreeNode node;
boolean isFirstVisit;
}
public List<Integer> postorderTraversal(TreeNode root) {
//travelSeq存放后序遍历的值序列
List<Integer> travelSeq = new ArrayList<Integer>();
if(root == null){
return travelSeq;
}
//声明一个辅助栈保存“包装”结点
Stack<CntTreeNode> treeStack = new Stack<CntTreeNode>();
TreeNode pNode = root;
CntTreeNode pCntNode = null;
while(pNode != null || !treeStack.empty()){
while(pNode != null){//所有左子入栈
//当第一次遇到当前结点,声明一个新的CntTreeNode结点,将原TreeNode包装进去,并初始化初始访问标识isFirstVisit为true
CntTreeNode newStackNode = new CntTreeNode();
newStackNode.node = pNode;
newStackNode.isFirstVisit = true;
//包装后的结点入栈
treeStack.push(newStackNode);
pNode = pNode.left;
}
//取结点
if(!treeStack.isEmpty()){
pCntNode = treeStack.pop();
if(pCntNode.isFirstVisit){
//若当前结点是第一次访问到,则说明以当前结点的左子树已经访问完毕,此时设置访问标识位,开始访问右子树
pCntNode.isFirstVisit = false;
treeStack.push(pCntNode);
pNode = pCntNode.node.right;
}else{
//若当前结点是第二次访问到,说明左右子树均已遍历完毕,此时应该visit当前结点
travelSeq.add(pCntNode.node.val);
//此时不再继续深入,此时应该从辅助栈中取出上一次的结点
pNode = null;
}
}
}
return travelSeq;
}
}
浙公网安备 33010602011771号