Morris遍历:用O(1)空间复杂度实现二叉树遍历

        在计算机科学中,树结构是非常常见的数据结构之一,特别是二叉树。对于二叉树的遍历,通常的方法包括递归和使用栈或队列等辅助数据结构。然而,这些方法往往需要额外的空间来存储中间结果。今天,我们将介绍一种不需要额外空间(除了用于函数调用的栈空间)的二叉树遍历方法——Morris遍历。


目录

一、传统遍历的问题

二、Morris遍历核心思想

三、Morris中序遍历(Java实现)

3.1 算法步骤

3.2 完整代码

四、Morris前序遍历实现

五、复杂度分析

六、应用场景与注意事项

适用场景:

注意事项:

七、总结


一、传统遍历的问题

        常规二叉树遍历(递归/栈实现)需要O(n)空间复杂度。当处理超大规模树结构时,空间消耗可能成为瓶颈!


二、Morris遍历核心思想

线索化二叉树:利用叶子节点的空指针,临时存储后续遍历节点的信息
三步核心操作

  1. 创建指向后继节点的线索

  2. 利用线索完成遍历

  3. 恢复树结构(关键!)


三、Morris中序遍历(Java实现)

3.1 算法步骤

  1. 初始化当前节点cur = root

  2. 当cur不为空时循环:

    • 如果cur无左子树:访问节点,cur右移

    • 如果cur有左子树:

      • 找到左子树的最右节点(前驱节点)

      • 若前驱右指针为空:建立线索,cur左移

      • 若前驱右指针为cur:断开线索,访问节点,cur右移

3.2 完整代码

public class MorrisTraversal {
    static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }
    }

    public static void morrisInOrder(TreeNode root) {
        TreeNode cur = root;
        while (cur != null) {
            if (cur.left == null) {
                // 无左子树直接访问
                System.out.print(cur.val + " ");
                cur = cur.right;
            } else {
                // 找到左子树的最右节点
                TreeNode predecessor = cur.left;
                while (predecessor.right != null && predecessor.right != cur) {
                    predecessor = predecessor.right;
                }

                if (predecessor.right == null) {
                    // 建立线索
                    predecessor.right = cur;
                    cur = cur.left;
                } else {
                    // 断开线索并访问
                    predecessor.right = null;
                    System.out.print(cur.val + " ");
                    cur = cur.right;
                }
            }
        }
    }

    // 测试用例
    public static void main(String[] args) {
        /*
                 1
               /      \
              2        3
            /   \    /   \
           4     5  6     7
        */
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.right = new TreeNode(5);
        root.right.left = new TreeNode(6);
        root.right.right = new TreeNode(7);

        System.out.println("Morris中序遍历:");
        morrisInOrder(root);  // 输出:4 2 5 1 6 3 7 
    }
}

四、Morris前序遍历实现

仅需调整节点访问时机(建立线索时访问):

public static void morrisPreOrder(TreeNode root) {
    TreeNode cur = root;
    while (cur != null) {
        if (cur.left == null) {
            System.out.print(cur.val + " ");
            cur = cur.right;
        } else {
            TreeNode predecessor = cur.left;
            while (predecessor.right != null && predecessor.right != cur) {
                predecessor = predecessor.right;
            }

            if (predecessor.right == null) {
                // 建立线索前访问节点
                System.out.print(cur.val + " ");
                predecessor.right = cur;
                cur = cur.left;
            } else {
                predecessor.right = null;
                cur = cur.right;
            }
        }
    }
}
 

五、复杂度分析

  • 时间复杂度:O(n)(每个节点被访问2次)

  • 空间复杂度:O(1)(仅使用固定数量指针)


六、应用场景与注意事项

适用场景:

  • 内存受限的超大规模树处理

  • 需要避免递归栈溢出的场景

注意事项:

  • 必须恢复树结构(否则会破坏原始树)

  • 不能用于存在环的树结构

  • 线程不安全(遍历过程中修改树结构)


七、总结

        Morris遍历通过临时线索化实现了空间复杂度突破,展现了算法设计的精妙思维。虽然实现稍复杂,但在特定场景下具有重要价值。建议配合调试工具观察指针变化加深理解!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值