二叉树各种遍历真的很难?大sai带你拿捏!(二)

joytrian
发布于 2022-7-6 17:04
浏览
0收藏

 

二叉树的后序遍历
二叉树的后序遍历非递归方式实现起来难度最大的,能够手写非递归后序,一定能亮瞎面试官的眼!

后序遍历在二叉树树的顺序可以看下图(红色箭头指向的表示需要访问的,可以看出如果子树为null,那肯定要访问,否则就是从右子树回来的时候才访问这个节点)。

二叉树各种遍历真的很难?大sai带你拿捏!(二)-鸿蒙开发者社区

递归
二叉树递归方式后序遍历很简单,跟前序中序的逻辑一样,在力扣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);
    }
}

非递归
非递归的后序就稍微难一点了,大家可以回顾一下二叉树的前序和中序遍历,其实都是只用一个栈直接抛出就可以找到右节点,抛出后栈就空了,但是这个后序遍历的顺序是 左子树 ---> 右子树 --->根节点,也就是处理完右节点,还要再回到根节点访问。所以从逻辑结构上和前序、中序的非递归实现方式有一些略有不同。

前序,中序遍历的顺序提取为:

前序: 中入栈—>左入栈—>左孩子入出—>左出栈—>中出栈—>右入栈—>右孩子入出—>右出栈

前序遍历同一大流程按照入栈顺序即形成一个前序序列

中序: 中入栈—>左入栈—>左孩子入出—>左出栈—>中出栈—>右入栈 —>右孩子入出—>右出栈

中序遍历同一大流程按照出栈顺序即形成一个中序序列

但是后序的话应该怎么考虑呢?

其实就是在从左孩子到中准备出栈的时候,先不出栈记成第一次,再将它放入栈中,如果从右孩子返回那么这个节点就是第三次遍历(第一次访问中,然后枚举左返回第二次,枚举右返回第三次),这时候将其抛出就形成一个后序。

如果不理解这里画了一个简单的图帮助理解:

二叉树各种遍历真的很难?大sai带你拿捏!(二)-鸿蒙开发者社区

思路理解了,怎么实现呢?最简单的就是使用一个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

标签
已于2022-7-6 17:04:20修改
收藏
回复
举报
回复
    相关推荐