二叉树各种遍历真的很难?大sai带你拿捏!(二)
二叉树的后序遍历
二叉树的后序遍历非递归方式实现起来难度最大的,能够手写非递归后序,一定能亮瞎面试官的眼!
后序遍历在二叉树树的顺序可以看下图(红色箭头指向的表示需要访问的,可以看出如果子树为null,那肯定要访问,否则就是从右子树回来的时候才访问这个节点)。
递归
二叉树递归方式后序遍历很简单,跟前序中序的逻辑一样,在力扣145有后序的code测试大家可以自己尝试一下。
这里直接放我写的后序递归方式:
class Solution {
List<Integer>value=new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
houxu(root);
return value;
}
private void houxu(TreeNode root) {
if(root==null)
return;
houxu(root.left);
houxu(root.right);//右子树回来
value.add(root.val);
}
}
非递归
非递归的后序就稍微难一点了,大家可以回顾一下二叉树的前序和中序遍历,其实都是只用一个栈直接抛出就可以找到右节点,抛出后栈就空了,但是这个后序遍历的顺序是 左子树 ---> 右子树 --->根节点,也就是处理完右节点,还要再回到根节点访问。所以从逻辑结构上和前序、中序的非递归实现方式有一些略有不同。
前序,中序遍历的顺序提取为:
前序: 中入栈—>左入栈—>左孩子入出—>左出栈—>中出栈—>右入栈—>右孩子入出—>右出栈
前序遍历同一大流程按照入栈顺序即形成一个前序序列
中序: 中入栈—>左入栈—>左孩子入出—>左出栈—>中出栈—>右入栈 —>右孩子入出—>右出栈
中序遍历同一大流程按照出栈顺序即形成一个中序序列
但是后序的话应该怎么考虑呢?
其实就是在从左孩子到中准备出栈的时候,先不出栈记成第一次,再将它放入栈中,如果从右孩子返回那么这个节点就是第三次遍历(第一次访问中,然后枚举左返回第二次,枚举右返回第三次),这时候将其抛出就形成一个后序。
如果不理解这里画了一个简单的图帮助理解:
思路理解了,怎么实现呢?最简单的就是使用一个hashmap存储节点访问次数。
附一下个人实现的代码:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> value=new ArrayList();
Stack<TreeNode> q1 = new Stack();
Map<TreeNode,Integer >map=new HashMap<>();
while(!q1.isEmpty()||root!=null)
{
if (root!=null) {
q1.push(root);
map.put(root, 1); //t.value标记这个值节点出现的次数
root=root.left;
}
else {
root=q1.peek();
if(map.get(root)==2) {//第二次访问,抛出
q1.pop();
value.add(root.val);
root=null;//需要往上走
}
else {
map.put(root, 2);
root=root.right;
}
}
}
return value;
}
}
但是这个情况如果面试官问你如果有hash冲突怎么办?虽然这种概率非常小几乎不会但是面试官不会放过你,但是还是要用正统方法来实现。
那么正统方法怎么解决呢?
也很容易,用一个pre节点一直保存上一次抛出访问的点,如果当前被抛出的右孩子是pre或者当前节点右为null,那么就将这个点抛出,否则就将它"回炉重造"一次!
实现代码为:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
TreeNode temp=root;//枚举的临时节点
List<Integer>value=new ArrayList<>();
TreeNode pre=null;//前置节点
Stack<TreeNode>stack=new Stack<>();
while (!stack.isEmpty()||temp!=null){
while(temp!=null){
stack.push(temp);
temp=temp.left;
}
temp=stack.pop();
if(temp.right==pre||temp.right==null)//需要弹出
{
value.add(temp.val);
pre=temp;
temp=null;//需要重新从栈中抛出
}else{
stack.push(temp);
temp=temp.right;
}
}
return value;
}
}
是不是觉得非常巧妙?那我再说一种骚操作的代码,你看 左右中,它反过来就是中右左,这不就是一个反的前序遍历嘛!所以进行一次反的前序遍历,然后将结果翻转一下也能得到这个值啦,当然,你使用双栈同时翻转也是一样的道理!
实现代码为:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer>value=new ArrayList();
Stack<TreeNode> q1 = new Stack();
while(!q1.isEmpty()||root!=null)
{
while (root!=null) {
value.add(root.val);
q1.push(root);
root=root.right;
}
root=q1.pop();//抛出
root=root.left;//准备访问其右节点
}
Collections.reverse(value);//将结果翻转
return value;
}
}
文章转自公众号:bigsai