二叉树的非递归遍历--百度面试有感

  上周百度内推招聘,无意间发了个简历,也没抱太大希望,三天后,收到面试通知。面试官是一个年级不大的人,戴着一副黑框眼镜,感觉还比较随和。废话不多说,面试就考到了二叉树的遍历,我开始心想,这不太easy了吧,写完后,懵逼了,面试官批评说递归遍历企业实际业务中是不会用的,你把二叉树后续遍历非递归形式写出。。。what!!!!!最难的那个!好吧,硬着头皮写了二十多分钟,还是没写出来,瞬间感觉自己low爆了。若要虐人,先被人虐,虽然没答上来,但还是提醒我平时要注意基础算法的掌握。后来回到实验室自己一个人苦思冥想,终于想出来了,把代码分享一下,欢迎各位大神斧正。

  后序遍历一个二叉树,递归比较简单,在此就不再赘述了。值得提醒的是,使用递归方法其本质是调用系统栈来保存断点,而系统栈区是有限的,对于深度比较小的树尚能承受,一旦遇到深度比较大的树就很容易出现系统栈溢出的情况,所以在公司业务场景中很少使用,或者不使用,仅存在理论上的意义。既然不能使用递归方式,但还是要深度优先遍历,每次项下一层结点的访问都需要保存上一层的结点,很容易想到使用栈来保存。二叉树非递归遍历本质就是让我们显示定义栈,然后维护栈中结点保证二叉树的后序遍历次序。

  但为什么说在二叉树的三种遍历方式中,非递归后序遍历难度最大呢? 这是因为先序遍历或者中序遍历在用栈辅助的非递归算法中,每个子树的根总是被访问一次,而再后序遍历中会访问过程中会遇到两次:

  1. 为了先访问左子树而经过根结点,此时不能visit根结点;
  2. 为了先访问右子树而经过根结点,此时可以进行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;
    }
}

  

posted on 2016-07-20 21:16  promise永久  阅读(194)  评论(0)    收藏  举报

导航