【二叉树的遍历-1】前序遍历(递归与非递归)
【二叉树的遍历-2】中序遍历(递归与非递归)
【二叉树的遍历-4】层序遍历(递归与非递归)
后序遍历
后序遍历(LRD)是二叉树遍历的一种,也叫做后根遍历、后序周游,可记做左右根。后序遍历有递归算法和非递归算法两种。在二叉树中,先左后右再根,即首先遍历左子树,然后遍历右子树,最后访问根结点。
所以后序遍历也只要记住一点::左子树–> 右子树 --> 根结点
递归实现:
和之前的中序遍历和先序遍历差不多,跟套模板一样,改变一下打印的位置就好。
具体代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class BinaryTree {
public void postorderTraversal(TreeNode root) {
if(root != null) {
postorderTraversal(root.left);
postorderTraversal(root.right);
System.out.println(root.val);
}
}
}
非递归实现:
跟中序非递归遍历超级像,但是后序遍历比它会多一个步骤,因为当遍历完某个根节点的左子树,回到根节点的时候,对于中序遍历可以把当前根节点直接从栈里弹出,然后转到右子树。而对于后序遍历左右子树遍历完都会回到根节点,所以就需要判断是从左子树到的根节点,还是右子树到的根节点。如果像中序遍历那样不加判断有些情况最后会造成死循环。
- 所以我们这一次在中序遍历的基础上加一个步骤,增加一个标志位,用它来记录上一次遍历的节点。
- 如果当前节点的右节点和上一次遍历的节点相同,那就表明当前是从右节点过来的了。此时就可以直接打印当前节点了。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class BinaryTree{
public void postorderTraversal(TreeNode root) {
Stack<TreeNode> s = new Stack<>();
TreeNode cur = root;
TreeNode lastTime = null; //用它来记录上一次遍历的节点,后面用来和当前节点比较,看看它是不是当前节点的右子树
while(cur != null || !s.empty()) { //cur不为空和栈不为空都说明节点没有遍历完,需要继续循环
while(cur != null) {
s.push(cur); //尽可能将节点左子树依次压入栈中
cur = cur.left;
}
//此时栈顶的元素是最左侧的元素,它的左子树为空,相当于左子树已经遍历了,但是不能直接打印。
//第一次当然很清楚是从左子树到达的根,因为它的左子树刚刚遍历完。
//但是后面有些有些情况无法判断它是从哪到的根,所以需要判断当前节点的上一个遍历节点是不是其右子树。
TreeNode temp = s.peek();
//右子树为空不用遍历可以直接打印,或者刚刚从右子树回来也可以直接打印当前节点
if(temp.right == null || temp.right == lastTime) {
System.out.println(temp.val);
lastTime = temp; //每次遍历都用lastTime保存,以便下次遍历时与节点右子树比较。
s.pop();
}else {
cur = temp.right; //右子树不为空且每遍历,就需要将右子树压栈遍历
}
}
}
}
附leetcode后序非递归遍历代码
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
Stack<TreeNode> s = new Stack<>();
TreeNode cur = root;
TreeNode lastTime = null;
while(cur != null || !s.empty()) {
while(cur != null) {
s.push(cur);
cur = cur.left;
}
TreeNode temp = s.peek();
if(temp.right == null || temp.right == lastTime) {
ret.add(temp.val);
lastTime = temp;
s.pop();
}else {
cur = temp.right;
}
}
return ret;
}
}
对于非递归遍历其实还有一种取巧的做法:
后序遍历的顺序是 :左子树–> 右子树 --> 根结点。
前序遍历的顺序是 根 节点–> 左子树–> 右子树,有这个,我们只需要稍微改变一下前序压栈的顺序就可以轻松的写出:根节点- -> 右子树 --> 左子树 的代码。
然后把:根节点- -> 右子树 --> 左子树逆序,就是左子树–> 右子树 --> 根结点了,也就是后序遍历了。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
if(root == null) {
return ret;
}
Stack<TreeNode> s = new Stack<>();
s.push(root);
while(!s.empty()) {
TreeNode cur = s.pop();
ret.add(cur.val);
if(cur.left != null) {
s.push(cur.left);
}
if(cur.right != null) {
s.push(cur.right);
}
}
//前面代码都是前序非递归遍历的代码,只是稍微改变了一下左右子树压栈顺序。
//得到根右左之后,直接反转顺序表,就是后序遍历了。
Collections.reverse(ret);
return ret;
}
}
但是不建议这样写,这个题解只是能返回遍历的结果,并不是严格意义上树拓扑结构的遍历。虽然结果是正确,但是如果真的严格需要按照后续遍历的顺序对树节点进行访问(或操作)时,此解法就无法满足。