• Morris二叉树遍历


    前言

      As we all know,二叉树遍历的方法有递归和利用栈实现的迭代版本,他们的时间和空间复杂度都为 O(n) 。

      然而,利用 Morris 遍历方法(利用叶子节点的左右空结点),可以将空间复杂度将为 O(1) ,时间复杂度仍为 O(n) 。

    实现

    前序遍历

      1. 如果当前节点左孩子 cur->left == NULL,输出当前节点的值 cur->value 并将当前结点指向右孩子 cur = cur->right。

      2. 如果当前节点左孩子 cur->left != NULL,那么在当前节点的左子树中找出最右叶子结点(即前驱结点 pre )。

        ① 如果前驱节点的右孩子 pre->right == NULL ,那么将右孩子指向当前节点(pre->right = cur)。

          输出当前节点的值 cur->value。当前节点更新为当前节点的左孩子 cur = cur->left。

         如果前驱节点的右孩子 pre->right != NULL,即 pre->right = cur。

          重新将右孩子设为空 pre->right == NULL。当前节点更新为当前节点的右孩子cur = cur->right。

      3. 重复 1、2 直到当前节点为空。

    代码

    vector<int> preorderTraversal(TreeNode* root) 
    {
        TreeNode* cur = root;
        TreeNode* pre = NULL;
        vector<int> result;
        while(cur!=NULL)
        {
            if (cur->left == NULL)
            {
                result.push_back(cur->val);
                cur = cur->right;
            }
            else
            {
                pre = cur->left;
                while(pre->right!=NULL && pre->right!=cur)
                {
                    pre = pre->right;
                }
                if (pre->right==NULL)
                {
                    pre->right=cur;
                    result.push_back(cur->val);
                    cur = cur->left;
                }
                else
                {
                    pre->right = NULL;
                    cur = cur->right;
                }
            }
        }
        return result;
    }

    中序遍历

      中序遍历与前序遍历相同,输出时机不同而已。

      1. 如果当前节点左孩子 cur->left == NULL,输出当前节点的值 cur->value 并将当前结点指向右孩子 cur = cur->right。

      2. 如果当前节点左孩子 cur->left != NULL,那么在当前节点的左子树中找出最右叶子结点(即前驱结点 pre )。

        ① 如果前驱节点的右孩子 pre->right == NULL ,那么将右孩子指向当前节点(pre->right = cur)。

          当前节点更新为当前节点的左孩子 cur = cur->left。

        ② 如果前驱节点的右孩子 pre->right != NULL,即 pre->right = cur。

          重新将右孩子设为空 pre->right == NULL。

          输出当前节点的值 cur->value。

          当前节点更新为当前节点的右孩子cur = cur->right。

      3. 重复 1、2 直到当前节点为空。

    代码

    vector<int> inorderTraversal(TreeNode* root) 
    {
        TreeNode* cur = root;
        TreeNode* pre = NULL;
        vector<int> result;
        while(cur!=NULL)
        {
            if (cur->left == NULL)
            {
                result.push_back(cur->val);
                cur = cur->right;
            }
            else
            {
                pre = cur->left;
                while(pre->right!=NULL && pre->right!=cur)
                {
                    pre = pre->right;
                }
                if (pre->right == NULL)
                {
                    pre->right=cur;
                    cur = cur->left;
                }
                else
                {
                    pre->right = NULL;
                    result.push_back(cur->val);
                    cur = cur->right;
                }
            }
        }
        return result;
    }

    后序遍历

      1. 新增临时结点dump,并且将 root 设为 dump 的左孩子。

      2. 如果当前节点左孩子 cur->left == NULL,输出当前节点的值 cur->value 并将当前结点指向右孩子 cur = cur->right。

      3. 如果当前节点左孩子 cur->left != NULL,那么在当前节点的左子树中找出最右叶子结点(即前驱结点 pre )。

        ① 如果前驱节点的右孩子 pre->right == NULL ,那么将右孩子指向当前节点(pre->right = cur)。

          当前节点更新为当前节点的左孩子 cur = cur->left。

        ② 如果前驱节点的右孩子 pre->right != NULL,即 pre->right = cur。

          逆序输出当前结点左孩子到前序结点的路径。

          重新将右孩子设为空 pre->right == NULL。当前节点更新为当前节点的右孩子cur = cur->right。

      4. 重复 2、3 直到当前节点为空。

    代码

    vector<int> postorderTraversal(TreeNode* root) 
    {
        TreeNode dump(-1);
        dump.left = root;
        TreeNode* cur = &dump;
        TreeNode* pre = NULL;
        vector<int> result;
        while(cur!=NULL)
        {
            if (cur->left == NULL)
            {
                cur = cur->right;
            }
            else
            {
                pre = cur->left;
                while(pre->right!=NULL && pre->right!=cur)
                {
                    pre = pre->right;
                }
                if (pre->right == NULL)
                {
                    pre->right=cur;
                    cur = cur->left;
                }
                else
                {
                    printReverse(cur->left, pre, result);
                    pre->right = NULL;
                    cur = cur->right;
                }
            }
        }
        return result;
    }
    
    void printReverse(TreeNode* from, TreeNode* to, vector<int>& result) //输出单链表
    {
        Reverse(from, to);
        TreeNode* p = to;
        while(true)
        {
            result.push_back(p->val);
            if(p == from)
            {
                break;
            }
            p = p->right;
        }
        Reverse(to, from);
    }
    
    void Reverse(TreeNode* from, TreeNode* to) //翻转单链表
    {
        TreeNode* x = from;
        TreeNode* y = from->right;
        TreeNode* z;
        if (from == to)
        {
            return;
        }
        x->right = NULL;
        while(x != to)
        {
            z = y->right;
            y->right = x;
            x = y;
            y = z;
        }
    }

    总结

      通过三种遍历可以看到,其实总体上的代码逻辑没有发生改变,主要是改变了输出结果的时机和方式。

      参考博主ouyangsong,这位博主关于 Morris 算法的内容已经写的非常好了。

  • 相关阅读:
    关于k12领域Lucene.net+pangu搜索引擎设计开发的一些回顾
    行业门户网站搜索引擎的一些设计开发体会
    快速体验,学习lua(一种可嵌入c++,c#,android,object-c等并进行互调支持热更新的脚本)的入门调试系列(3)
    快速体验,学习lua(一种可嵌入c++,c#,android,object-c等并进行互调支持热更新的脚本)的入门调试系列(2)
    快速体验,学习lua(一种可嵌入c++,c#,android,object-c等并进行互调支持热更新的脚本)的入门调试系列(1)
    记录下自己在Android Studio3.2中导出jar包的过程
    分享下常见的app在手机上安装不上的原因
    关于各种不同开发语言之间数据加密方法(DES,RSA等)的互通的解决方案(c++共享类库方案)
    关于如何解决:服务器上DateTime.Now获取的时间不是北京标准时间的问题
    分享一个自己项目中用到的c++版的日志类(对初学者十分有用的,有问题的可以留言)
  • 原文地址:https://www.cnblogs.com/VividBinGo/p/12502591.html
Copyright © 2020-2023  润新知