Morris Traversal方法遍历二叉树

本文介绍了Morris遍历方法,这是一种非递归且空间复杂度为O(1)的二叉树遍历算法。详细解析了中序、前序和后序遍历的实现过程,并分析了其时间复杂度和空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考点击打开链接

Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)。

通常,在实现二叉树的前序、中序和后序遍历时,有两种常用方法,一种是递归,一种是使用栈+迭代。这两种方法都是O(n)的空间复杂度(递归本身占用栈空间或者用户自定义的栈)。

Morris Traversal可以做到:

1、O(1)空间复杂度,只使用常数空间;O(n)时间复杂度。

2、不破坏二叉树的形状(中间过程允许改变其形状)。

要使用O(1)空间进行遍历,最大的难点在于遍历到子结点的时候如何返回到父节点,Morris Traversal方法不需要为每个节点额外分配指针指向其前驱结点和后继结点,只需要利用叶子结点中的左右空指针指向某种顺序遍历下的前驱结点和后继结点就可以了。

中序遍历:

1、如果当前结点的左孩子为空,则输出当前结点并将其右孩子作为当前结点。

2、如果当前结点的左孩子不为空,在当前结点的左子树中找到当前结点在中序遍历下的前驱结点。

a) 如果前驱结点的右孩子为空,则它的右孩子设置为当前结点。当前结点更新为当前结点的左孩子;

b) 如果前驱结点的右孩子为当前结点,则将它的右孩子重新设为空(恢复树的形状)。输出当前结点,当前结点更新为当前结点的右孩子。

3、重复以上1、2直到当前结点为空。

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

class Solution {
    public static void main(String[] args) {
        Solution s = new Solution();
        TreeNode p1 = new TreeNode(1);
        TreeNode p2 = new TreeNode(2);
        TreeNode p3 = new TreeNode(3);
        TreeNode p4 = new TreeNode(4);
        TreeNode p5 = new TreeNode(5);
        TreeNode p6 = new TreeNode(6);
        TreeNode p7 = new TreeNode(7);
        TreeNode p8 = new TreeNode(8);
        TreeNode p9 = new TreeNode(9);
        p2.left = p1; p2.right = p4;
        p4.left = p3; p4.right = p5;
        p6.left = p2; p6.right = p7;
        p7.right = p9;
        p9.left = p8;
        s.inorderMorrisTraversal(p6);
    }
    public void inorderMorrisTraversal (TreeNode root)
    {
        TreeNode curr = root;
        TreeNode pre = null;
        while (curr != null)
        {
            if (curr.left == null)          //1.
            {
                System.out.println(curr.val);
                curr = curr.right;
            }
            else
            {
                pre = curr.left;
                while (pre.right != null && pre.right != curr)
                    pre = pre.right;
                if (pre.right == null)       //2. a)   
                {
                    pre.right = curr;
                    curr = curr.left;
                }
                else                         //2. b)
                {
                    pre.right = null;
                    System.out.println(curr.val);
                    curr = curr.right;
                }
            }
        }
    }
}

复杂度分析:

空间复杂度:O(1),只使用了两个结点变量

时间复杂度:O(n),在于寻找中序遍历下二叉树中所有结点的前驱结点的复杂度是多少。

直接上时间复杂度为O(nlogn),找到单个结点的前驱结点和树的高度有关。但事实上,寻找所有结点的前驱结点只需要O(n)时间。n个结点的二叉树中一共有n-1条边,整个过程中每条边最多只走2次,一次是为定位到某个结点,另一次是为了寻找上面某个结点的前驱结点,所以时间复杂度为O(n)。


前序遍历:

1、如果当前结点的左孩子为空,则输出当前结点并将其右孩子作为当前结点。

2、如果当前结点的左孩子不为空,则在当前结点的左子树中找到当前结点在中序遍历下的前驱结点。

a) 如果前驱结点的右孩子为空,将它的右孩子设置为当前结点。输出当前结点。更新当前结点为当前结点的左孩子;

b) 如果前驱结点的右孩子为当前结点,将它的右孩子重新设为空。当前结点更新为当前结点的右孩子。

3、重复以上1、2直到当前结点为空。

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

class Solution {
    public static void main(String[] args) {
        Solution s = new Solution();
        TreeNode p1 = new TreeNode(1);
        TreeNode p2 = new TreeNode(2);
        TreeNode p3 = new TreeNode(3);
        TreeNode p4 = new TreeNode(4);
        TreeNode p5 = new TreeNode(5);
        TreeNode p6 = new TreeNode(6);
        TreeNode p7 = new TreeNode(7);
        TreeNode p8 = new TreeNode(8);
        TreeNode p9 = new TreeNode(9);
        p2.left = p1; p2.right = p4;
        p4.left = p3; p4.right = p5;
        p6.left = p2; p6.right = p7;
        p7.right = p9;
        p9.left = p8;
        s.preorderMorrisTraversal(p6);
    }
    public void preorderMorrisTraversal (TreeNode root)
    {
        TreeNode curr = root;
        TreeNode pre = null;
        while (curr != null)
        {
            if (curr.left == null)          //1.
            {
                System.out.println(curr.val);
                curr = curr.right;
            }
            else
            {
                pre = curr.left;
                while (pre.right != null && pre.right != curr)
                    pre = pre.right;
                if (pre.right == null)       //2. a)
                {
                    System.out.println(curr.val);
                    pre.right = curr;
                    curr = curr.left;
                }
                else                         //2. b)
                {
                    pre.right = null;
                    curr = curr.right;
                }
            }
        }
    }
}
复杂度分析:

时间复杂度和空间复杂度都与中序遍历时的情况相同。


后序遍历:

建立一个临时结点dump,令其左孩子是root。并且还需要一个子过程,倒序输出两个结点之间路径上的各个结点。

当前结点设置为临时结点dump。

1、如果当前结点的左孩子为空,则将其右孩子作为当前结点。

2、如果当前结点的左孩子不为空,在当前结点的左子树中找到当前结点在中序遍历下的前驱结点。

a) 如果前驱结点的右孩子为空,将它的后孩子设置为当前结点。当前结点更新为当前结点的左孩子;

b) 如果前驱结点的右孩子为当前结点,将它的右孩子重新设为空。倒序输出当前结点的左孩子到该前驱结点这条路径上的所有结点。当前结点更新为当前结点的右孩子。

3、重复以上1、2直到当前结点为空。

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

class Solution {
    public static void main(String[] args) {
        Solution s = new Solution();
        TreeNode p1 = new TreeNode(1);
        TreeNode p2 = new TreeNode(2);
        TreeNode p3 = new TreeNode(3);
        TreeNode p4 = new TreeNode(4);
        TreeNode p5 = new TreeNode(5);
        TreeNode p6 = new TreeNode(6);
        TreeNode p7 = new TreeNode(7);
        TreeNode p8 = new TreeNode(8);
        TreeNode p9 = new TreeNode(9);
        p2.left = p1; p2.right = p4;
        p4.left = p3; p4.right = p5;
        p6.left = p2; p6.right = p7;
        p7.right = p9;
        p9.left = p8;
        s.postorderMorrisTraversal(p6);
    }
    public void reverse(TreeNode from, TreeNode to)
    {
        if (from == to)
            return;
        TreeNode x = from;
        TreeNode y = from.right;
        TreeNode z;
        while (true)
        {
            z = y.right;
            y.right = x;
            x = y;
            y = z;
            if (x == to) break;
        }
    }
    public void printReverse(TreeNode from, TreeNode to)
    {
        reverse(from, to);
        TreeNode p = to;
        while (true)
        {
            System.out.println(p.val);
            if (p == from) break;
            p = p.right;
        }
        reverse(to, from);
    }
    public void postorderMorrisTraversal (TreeNode root)
    {
        TreeNode dump = new TreeNode(0);
        dump.left = root;
        TreeNode curr = dump;
        TreeNode pre = null;
        while (curr != null)
        {
            if (curr.left == null)          //1.
            {
                curr = curr.right;
            }
            else
            {
                pre = curr.left;
                while (pre.right != null && pre.right != curr)
                    pre = pre.right;
                if (pre.right == null)       //2. a)
                {
                    pre.right = curr;
                    curr = curr.left;
                }
                else                         //2. b)
                {
                    printReverse(curr.left, pre);
                    pre.right = null;
                    curr = curr.right;
                }
            }
        }
    }
}

复杂度分析:

空间复杂度O(1);

时间复杂度O(n)。倒序输出加大了常数系数。

### Java中实现二叉树中序遍历方法 #### 方法概述 在Java中,可以通过多种方法实现二叉树的中序遍历。常见的方法包括递归、迭代以及莫里斯遍历Morris Traversal)。以下是每种方法的具体实现。 --- #### 1. **递归实现** 递归是最直观的方式之一,其核心思想是按照“左子树 -> 根节点 -> 右子树”的顺序访问节点[^2]。 ```java public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); helper(root, result); return result; } private void helper(TreeNode node, List<Integer> resultList) { if (node != null) { helper(node.left, resultList); // 访问左子树 resultList.add(node.val); // 处理当前节点 helper(node.right, resultList); // 访问右子树 } } ``` 这种方法的时间复杂度为O(n),空间复杂度取决于递归调用栈的深度,最坏情况下为O(h),其中h为树的高度[^4]。 --- #### 2. **迭代实现** 为了减少递归带来的额外开销,可以使用显式的栈来模拟递归过程。这种方式同样遵循“左子树 -> 根节点 -> 右子树”的原则[^1]。 ```java import java.util.*; public List<Integer> inorderTraversalIterative(TreeNode root) { List<Integer> result = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); TreeNode current = root; while (current != null || !stack.isEmpty()) { while (current != null) { // 将左子树压入栈中 stack.push(current); current = current.left; } current = stack.pop(); // 弹出并处理当前节点 result.add(current.val); current = current.right; // 转向右子树 } return result; } ``` 该方法的空间复杂度由使用的栈决定,时间复杂度仍为O(n)[^1]。 --- #### 3. **莫里斯遍历Morris Traversal)** 莫里斯遍历是一种不需要额外存储空间的高效算法,通过修改树中的指针完成遍历。它利用叶子节点的空闲指针指向其他节点,从而避免了使用栈或递归[^2]。 ```java public List<Integer> morrisInorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); TreeNode current = root; while (current != null) { if (current.left == null) { // 如果没有左子树,则直接访问当前节点 result.add(current.val); current = current.right; } else { TreeNode predecessor = findPredecessor(current); // 找到前驱节点 if (predecessor.right == null) { // 建立临时链接 predecessor.right = current; current = current.left; } else { // 断开临时链接并访问当前节点 predecessor.right = null; result.add(current.val); current = current.right; } } } return result; } // 寻找当前节点的前驱节点 private TreeNode findPredecessor(TreeNode node) { TreeNode pre = node.left; while (pre.right != null && pre.right != node) { pre = pre.right; } return pre; } ``` 这种算法的空间复杂度为O(1),因为不依赖于任何额外的数据结构。 --- ### 性能对比 | 方法 | 时间复杂度 | 空间复杂度 | |---------------|------------|-------------| | 递归 | O(n) | O(h) | | 迭代(栈) | O(n) | O(h) | | 莫里斯遍历 | O(n) | O(1) | 以上表格展示了不同方法的性能特点,开发者可以根据实际需求选择合适的方案[^1]。 --- ### 结论 对于初学者来说,递归和基于栈的迭代方法更为直观易懂;而对于追求极致性能的应用场景,莫里斯遍历则是一个不错的选择。无论采用哪种方法,都应确保代码逻辑清晰且易于维护。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值