二叉树的Morris遍历,空间复杂度为O(1)

1. 概述
二叉树的常见遍历方法有递归和使用栈的非递归两种
这两种递归方法的时间复杂度都是O(n);
空间复杂度都是O(log n)

有一种遍历——Morris遍历,利用二叉树的数据结构特性,使用O(1)的空间即可完成遍历。

首先提出前序结点的概念:
在中序遍历时,当前结点的前一个结点即为它的前序结点。
查找前序结点的步骤:

若当前结点node的左子结点不为空,则令pre = node.left;若pre没有右子结点,则它就为node的前序结点;若pre有右子结点,则沿着pre的右子结点一直向右,到达最右,该结点即为node的前序结点;
若当前结点node的左子结点为空,则其没有前序结点
Morris遍历的算法步骤:

首先令 node = root;
如果node左子结点为空,则直接进入node的右子结点
如果node左子结点不为空,则查找它的前序结点pre;
如果pre.right == null, 则令pre.right = node,即用pre的右子结点指向当前结点node;然后node进入当前结点的左子结点;
如果pre.right != null (pre.right要么为null,要么等于当前结点node),则令pre.right = null,node进入右子结点;
Morris遍历利用前序结点的右子结点作为标记:

当前序结点的右子结点为null,意味着还没有对当前结点的左子树进行遍历,用pre的右子结点指向当前结点,以便从左子树返回时找到当前结点以及标记pre的右子结点为非null;
当前序结点的右子结点不为null(那么它必为当前结点node),这意味着已经遍历过了当前结点的左子树,不用再对它做什么操作了,接下来进入当前结点的右子树进行遍历;
 

2. 前序遍历
public List<Integer> morrisPreorder(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    TreeNode node = root;
    while (node != null) {
        if (node.left == null) {
            res.add(node.val);
            node = node.right;
        } else {
            TreeNode pre = getPrenode(node);
            if (pre.right == null) {
                res.add(node.val);
                pre.right = node;
                node = node.left;
            } else {
                pre.right = null;
                node = node.right;
            }
        }
    }
    return res;
}

private TreeNode getPrenode(TreeNode node) {
    TreeNode pre = node.left;
    while (pre.right != null && pre.right != node) pre = pre.right;
    return pre;
}

前序遍历时:
若当前结点左子结点为空,则就对当前结点遍历;
否则,当第一次到达当前结点node,即对其遍历,因次在pre.right == null时就对当前结点遍历;

3. 中序遍历
public List<Integer> morrisInorder(TreeNode root) {
   List<Integer> res = new ArrayList<>();
    TreeNode node = root;
    while (node != null) {
        if (node.left == null) {
            res.add(node.val);
            node = node.right;
        } else {
            TreeNode pre = getPrenode(node);
            if (pre.right == null) {
                pre.right = node;
                node = node.left;
            } else {
                res.add(node.val);
                pre.right = null;
                node = node.right;
            }
        }
    }
    return res;
}

private TreeNode getPrenode(TreeNode node) {
    TreeNode pre = node.left;
    while (pre.right != null && pre.right != node) pre = pre.right;
    return pre;
}

中序遍历:
若当前结点左子结点为空,则就对当前结点遍历(先左再根后右);
否则,当第二次到达当前结点node,即对其遍历,因次在pre.right != null时就对当前结点遍历;第二次意味着已经完成了当前结点左子树的遍历,接下来就是遍历当前结点了;

4. 后序遍历
public List<Integer> morrisPostorder(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    TreeNode dummy = new TreeNode(-1);
    dummy.left = root;
    TreeNode node = dummy;
    while (node != null) {
        if (node.left == null) {
            node = node.right;
        } else {
            TreeNode pre = getPrenode(node);
            if (pre.right == null) {
                pre.right = node;
                node = node.left;
            } else {
                printReverse(node.left, pre, res);
                pre.right = null;
                node = node.right;
            }
        }
    }
    return res;
}

private TreeNode getPrenode(TreeNode node) {
    TreeNode pre = node.left;
    while (pre.right != null && pre.right != node) pre = pre.right;
    return pre;
}

private void printReverse(TreeNode leftNode, TreeNode pre, List<Integer> res) {
    reverse(leftNode, pre);
    TreeNode node = pre;
    res.add(node.val);
    while (node != leftNode) {
        node = node.right;
        res.add(node.val);
    }
    reverse(pre, leftNode);
}

private void reverse(TreeNode node1, TreeNode node2) {
    if (node1 == node2) return;
    TreeNode x = node1;
    TreeNode y = node1.right;
    while (x != node2) {
        TreeNode temp = y.right;
        y.right = x;
        x = y;
        y = temp;
    }
}

Morris后序遍历要复杂一些:

后序遍历要求先左再右最后根结点;
因此,采用在回到根结点后,再对根结点从 该根结点的前序结点到该根结点的左子树遍历
因为在后序遍历中先访问右子树中的结点,再回到其上一层的根结点;
因此在这里,先将从左子结点到前序结点这一条一直向右的边的方向反转,方便遍历;遍历完成后再将其反转回去;
要到再次回到根结点后才能对其左子树的那条最右的边进行遍历,因此最后整棵树的最右那条边将不能被访问到;
因此,建立一个虚根结点,将整棵树作为它的左子树,这样,当再次回到虚根结点后,就可以对这棵树的最右那条边进行访问了。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值