后续遍历的递归实现
后续遍历指的是先访问节点的左右孩子,最后访问节点本身。所以使用后序遍历得到的结果的最后一个节点就是根节点。采用后续遍历的具体步骤如下:
- 先访问根节点,如果有左孩子,进入第二步;如果有右孩子,进入第三步
- 对左孩子继续判断其是否有左孩子,直到某节点的左孩子为空,设为cur节点
- 对右孩子继续判其是否有左孩子,直到某个节点的左孩子为空,设为curR节点
- cur节点访问之后,访问其双亲节点的右孩子,如果其双亲节点的右孩子不为空的话。接着访问cur节点的双亲节点,设为curP节点。
- cur节点的双亲节点访问结束之后,继续访问curP节点的右孩子(如果不为空的话),接着访问curP节点本身
- 重复以上过程直到根节点
- 继续访问根节点的右孩子,回到第三步,访问curR节点之后,访问其双亲节点的右孩子(如果有的话)
- 最后访问curR的双亲节点本身
重复以上过程直到访问根节点,访问结束
比如下面的二叉树后序遍历的访问结果是:24,15,5,7,6,30,9,28,11,10,8
那么使用递归实现的代码如下:
public void postOrderTraverse(TreeNode node){
if(node == null) return;
postOrderTraverse(node.left);
postOrderTraverse(node.right);
System.out.print(node.val + " ");
}
后续遍历的非递归实现
后续遍历的非递归实现与前面的前序遍历和中序遍历的方式有一些不同,由于根节点是最后访问的,在访问的时候创建一个栈保存遍历的结果还不够,所以需要使用一个辅助栈来记住每个访问节点的双亲节点。先看代码:
public void postOrderTraverse2(TreeNode node){
Stack<TreeNode> s1 = new Stack<TreeNode>();
//创建栈s2的目的在于记住每个访问的节点
Stack<Integer> s2 = new Stack<Integer>();
//如果栈s2的栈顶是1,标识当前访问的节点
Integer i = new Integer(1);
while(node != null || !s1.isEmpty()){
while(node != null){
s1.push(node);
s2.push(0);
node = node.left;
}
//这个循坏的目的是对栈s2栈顶为1时对应的栈s1的栈顶元素进行访问
while(!s1.isEmpty() && s2.peek().equals(i)){
s2.pop();
System.out.print(s1.pop().val + " ");
}
//访问左子树到头后,就可以访问其右孩子了
if(!s1.isEmpty()){
s2.pop();
s2.push(i);
node = s1.peek(); //peek()用于查找栈顶元素,无需弹出
node = node.right;
}
}
}
下面具体说明具体的执行过程:
根节点8不为空,放入栈s1中,同时往栈s2中放入0,由于8的左孩子是6不为空,所以继续放入栈s1中,同时往栈s2中放入0,直到节点15,由于其左孩子为空,所以第一个循坏结束,现在两个栈的情况是这样的:
由于栈s2的栈顶元素是0,不符合,所以第二个循环不执行,直接跳到if判断语句,把栈s2的栈顶元素弹出,同时压入元素1,并且取出栈s1的栈顶元素,也就是15,访问15的右孩子24
第二次循环开始,仍然满足第一个循环,于是把24放入栈s1,同时在栈s2压入一个0,由于24是叶子节点,所以第一个循环结束。前两次操作执行完毕后,两个栈的情况是这样的:
由于栈s2的栈顶元素仍然是0,所以第二个循环继续跳过,到if判断语句。弹出栈s2的栈顶元素0,同时压入元素1。并让当前节点指向24的右孩子
第三次循环开始,由于24没有右孩子,所以将跳过第一个循环,现在栈s2的栈顶元素是1,所以弹出栈s1的栈顶元素和栈s2的栈顶元素1,并输出24。执行以上两步之后,两个栈时这样的:
继续执行第二个while循环(因为栈s2的栈顶仍然是1),弹出1,弹出15,接着输出15。循环结束
进入if判断语句,弹出栈顶元素0,压入元素1,并让当前节点指向5的右孩子。这时两个栈时的情况如下:
第四次循环开始,由于5没有右孩子,第一个循环跳过,这时栈s2的栈顶是1,所以将弹出1,弹出5,接着输出5。这时两个栈是这样的:
9. 继续执行if语句,栈s2弹出0,压入1,并让当前节点指向6的右孩子,也就是节点7
10. 开始第五次循环,首先第一个循环,将7压入栈s1中,同时栈s2压入0。栈s2的栈顶元素是0,不满足第二个循环的条件,执行if语句。此时两个栈的情况如下:
11. 弹出0,压入1,并让当前节点指向7的右孩子
12. 开始第六次循环,7没有右孩子,直接执行第二个循环,将执行两次,栈s1分别弹出7和6,并输出依次输出7和6,同时栈s2分别弹出两个1,最后栈的情况是这样的:
13. 执行if语句,弹出0,压入1,并让当前节点指向8的右孩子,也就是10。后面的过程与上面类似,看图就好了