【二叉查找树/leetcode#538】图解Morris保姆级教学

本文介绍了Morris算法,一种用于二叉树遍历的优化方法,可以将空间复杂度从O(n)降低到O(1)。详细解释了如何在LeetCode#538中使用此算法将二叉搜索树转化为累加树,并提供了C++实现。通过逆中序遍历简化操作,避免了传统方法的时间和空间消耗。

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

Morris: 二叉树遍历的更好方案

平台:C++

Morris算法是什么

一种用于二叉树的遍历算法,可以将传统的栈遍历和递归遍历带来的最差 O ( n ) O(n) O(n)的空间复杂度降低到 O ( 1 ) O(1) O(1)

Morris’s in General

Morris算法抛弃了栈结构(Stack)来做DFS(Depth First Search),转而利用指针的连接来存取前驱节点

来源题解:Leetcode#538官方题解

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

例如:

输入: 原始二叉搜索树:
              5
            /   \
           2     13

输出: 转换为累加树:
             18
            /   \
          20     13

常规思路

由于给出的是二叉搜索树,右子树节点的值永远大于根节点,因此可以很容易想到中序遍历

(img-6vbzKZla-1600684108662)(./images/IT.png)]

遍历得到数组后再逆序相加,再填回原树

逆中序遍历

上面的方法行之有效,但是由于操作繁杂冗余,会消耗大量的时间,因此我们可以把逆序相加与遍历相结合,便诞生了逆中序遍历的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s7GtRjC5-1600684108664)(./images/RIT.png)]


Morris

解决了时间复杂度,是否还有优化的可能?
Morris算法就是这把钥匙。

代码概览(C++):


struct TreeNode {
      int val;
      TreeNode *left;
      TreeNode *right;
      TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  };

class Solution {
public:
    TreeNode* getPredecessor(TreeNode* node) {
        //Discard the stack to find the predecessor

        //Get the most left node of  right sub-trees as the predecessor
        TreeNode* pred = node->right;
        while (pred->left != nullptr && pred->left != node) {
            pred = pred->left;
        }
        return pred;
    }

    TreeNode* convertBST(TreeNode* root) {
        
        int sum = 0;

        //Initialise
        TreeNode* node = root;
        //
        TreeNode* pred;

        while (node != nullptr) {
            if (node->right == nullptr) {
                //Process the node
                sum += node->val;
                node->val = sum;
                //Proceed to the left node
                node = node->left;
            
            } else {
                //Get the most left node of  right sub-trees as the predecessor
                pred = getPredecessor(node);
                
                //connect the predecessor with the node to keep the predecessor
                if (pred->left == nullptr) {
                    pred->left = node;
                    // move node to the right node
                    node = node->right;
                } else {
                    // discard the link
                    pred->left = nullptr;
                    //process the node
                    sum += node->val;
                    node->val = sum;
                    node = node->left;
                }
            }
        }

        return root;
    }
};

接下来我们分步讲解Morris’s算法

  1. 初始化node为root,记录sum值为0
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9JwVi9w-1600684108665)(./images/Morris/M1.png)]

    并找到前驱节点

    TreeNode* getPredecessor(TreeNode* node) {
            //Discard the stack to find the predecessor
    
            //Get the most left node of  right sub-trees as the predecessor
            TreeNode* pred = node->right;
            while (pred->left != nullptr && pred->left != node) {
                pred = pred->left;
            }
            return pred;
        }
    

    前驱节点无左子节点,将前驱节点的左指针改到当前节点,这里是一个后续操作的伏笔
    并将node移动到右子节点

    pred = getPredecessor(node);
    if (pred->left == nullptr) {
        pred->left = node;
        // move node to the right node
        node = node->right;
        } 
    

    (img-Eo4NhytH-1600684108667)(./images/Morris/M2.png)]

  2. 继续找到node的前驱节点,并连接到node(逻辑同1)
    将node移动到右子节点
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GWvyfhCf-1600684108668)(./images/Morris/M3.png)]

  3. 此时右子节点到头了,应该对该右子节点进行处理
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ckqr3Olm-1600684108669)(./images/Morris/M4.png)]

    刚才埋下的伏笔即连接前驱节点,将发挥功效。
    通过此时node已经有了左指针node可以顺着左指针回到父节点
    在这里插入图片描述

        if (node->right == nullptr) {
                //Process the node
                sum += node->val;
                node->val = sum;
                //Proceed to the left node
                node = node->left;
            } 
    
  4. 接下来要做这些事

    1. 销毁前驱节点的左指针
    2. 对父节点进行处理
    3. 将node移动到其左子节点
    else {
        // discard the link
        pred->left = nullptr;
        //process the node
        sum += node->val;
        node->val = sum;
    }

在这里插入图片描述

  1. 同样的对打通了左子节点的11节点进行操作
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9w6Pn8H9-1600684108672)(images/Morris/M7.png)]

  2. 回溯到根节点进行操作
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LrAJLrSZ-1600684108672)(images/Morris/M8.png)]

  3. 对左子树对操作大同小异,无非是从2节点开始找前驱节点,在此省略

最后结果累加树

          49
       /     \
      54      33
     / \     /   \ 
   55   52   44  20

         

总结

Morris算法的思想,是充分利用数据结构的闲置资源(这里则是nullptr指针)。
因此,在算法的优化过程中,若是以压缩空间复杂度为目标,可以寻找数据结构本身存在的闲置资源


祝愿大家早日寻得内心的美好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值