二叉树遍历 Morris算法 空间O(1)

Morris算法通过建立与收回线索进行遍历,无需栈或队列,仅需几个指针,可以做到空间O(1)
二叉树遍历迭代法请参考
在这里插入图片描述

Morris前序

leetcode144. 二叉树的前序遍历

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        TreeNode cur = root;
        while(cur != null){
            if(cur.left != null){ //cur有左子树
                TreeNode pre = cur.left;
                while(pre.right!=null && pre.right!=cur) pre = pre.right;
                if(pre.right==null){ 
                    //第一次来
                    list.add(cur.val); //输出cur
                    pre.right = cur;   //建立线索
                    cur = cur.left;    //继续处理左子树
                }else{
                    //第二次来
                    pre.right = null;  //收回线索
                    cur = cur.right;   //去右子树
                }
            }else{
                //cur无左子树
                list.add(cur.val); //输出cur
                cur = cur.right;   //去右子树
            }
        }
        return list;
    }
}

Morris中序

leetcode94. 二叉树的中序遍历

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) { //Morris中序遍历
        List<Integer> list = new ArrayList<>();
        TreeNode cur = root; //当前结点
        TreeNode pre = null; //中序线索前驱
        while(cur != null){
            if(cur.left == null){ //当前结点无左子树
                list.add(cur.val); //直接输出
                cur = cur.right; //往右子树遍历
            }else{
                pre = cur.left; //当前结点有左子树,pre初始指向左孩子
                //pre右子树不为空,且右指针不指向cur
                while(pre.right != null && pre.right != cur){
                    pre = pre.right; //pre继续往右下
                }
                if(pre.right == null){ //右指针为空,第一次来该结点,往下层建立线索的阶段
                    pre.right = cur; //建立线索,pre结点为cur的中序线索前驱
                    cur = cur.left; //当前结点往左子树继续建立线索
                }else if(pre.right == cur){ //右指针指向cur,第二次来该结点,往上层边输出边收回线索
                    list.add(cur.val); //输出
                    pre.right = null; //收回线索
                    cur = cur.right; //左子树遍历完毕,去右子树
                }
            }
        }
        return list;
    }
}

Morris后序

leetcode145. 二叉树的后序遍历
Morris后序算法较为复杂,需要两个辅助方法。
熟悉后序输出顺序的话可知:后序输出序列最后是反向输出该树的最右侧一线,而Morris是以中序顺序为基准,并且不使用辅助栈/队列,否则失去Morris节省空间的意义。
所以在输出最右侧一线时:

  1. 先反转最右侧一线的顺序
  2. 输出这一线
  3. 再反转回来

注意此时千万不要想象树的结构,把最右侧一线当成一个链表理解。
因为在反转过程中,树的结构会被打乱。

printRight(TreeNode node):反向输出最右侧一线
reverse(TreeNode root):反转最右侧一线

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new LinkedList<>();
        TreeNode cur = root;
        while(cur != null){
            if(cur.left != null){
                //cur有左子树
                TreeNode p = cur.left;  //用p来找左子树最右侧结点
                while(p.right!=null && p.right!=cur) p = p.right;
                if(p.right == null){
                    //第一次来
                    p.right = cur;  //建立索引
                    cur = cur.left; //继续处理左子树
                }else{
                    //第二次来
                    p.right = null; //收回索引
                    printRight(cur.left,list);  //输出其左子树的最右侧一线
                    cur = cur.right; //去处理右子树
                }
            }else{
                //cur没有左子树
                cur = cur.right; //去处理右子树
            }  
        }
        printRight(root,list); //最后输出整棵树的最右侧一线
        return list;
    }

    private void printRight(TreeNode node , List<Integer> list){ //输出node结点的最右侧一线
        TreeNode tail = reverse(node); //反转最右侧一线
        TreeNode cur = tail;    //cur初始为反转后的根结点
        while(cur != null){
            list.add(cur.val);  //输出
            cur = cur.right;    //指向其右孩子
        }
        reverse(tail); //再反转回来
    }

    private TreeNode reverse(TreeNode root){ //反转node为根结点的最右侧一线
        TreeNode cur = root;     //当前需处理的结点,初始为root
        TreeNode parent = null;  //指向cur的父节点,初始为null,最终为翻转后的根结点
        TreeNode child = null;   //指向cur的右孩子结点,初始为null
        while(cur != null){      
            child = cur.right;   //先记录当前结点cur的右孩子
            cur.right = parent;  //反转,cur的右指针指向双亲
            parent = cur;        //父指针移动,为下一轮做准备
            cur = child;         //cur指向其原本的右孩子,为下一轮做准备
        }
        return parent;  //翻转后的根结点
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值