Morris 遍历算法(新手分析)[c++]

一、初步了解Morris 算法思想

以二叉树前序遍历为例:

Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。其前序遍历规则总结如下:

  1. 新建临时节点,令该节点为 root;

  1. 如果当前节点的左子节点为空,将当前节点加入答案,并遍历当前节点的右子节点;

  1. 如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点:

  • 如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点。然后将当前节点加入答案,并将前驱节点的右子节点更新为当前节点。当前节点更新为当前节点的左子节点。

  • 如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。当前节点更新为当前节点的右子节点。

  1. 重复步骤 b 和步骤 c,直到遍历结束。

这样我们利用 Morris 遍历的方法,前序遍历该二叉树,即可实现线性时间与常数空间的遍历。

二、图例分析

刚接触Morris 遍历算法,仅仅根据上述核心思想也许并不能融会贯通,个人经验是了解核心思想后自己试试能完成几步Morris 算法的核心编程,然后再自己制作图例或网上寻找资源,通过对整个过程图的一步步分析,理清哪里不懂,哪里有问题······

推荐:二叉树遍历高级算法之Morris---莫里斯算法

里面内容非常充实。

三、代码实践

以力扣144. 二叉树的前序遍历为例(分享下个人第一次的书写过程):

(1)建立连接

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> temp;
        if(root==nullptr)
        {
            return temp;
        }
        TreeNode* p1 = root, * p2 = nullptr;//分别用2个指针记录节点位置

        //第一步,建立连接
        while()//循环条件暂且不知
        {
            p2=p1->left;
            if(p2)//p2不为空指针
            {
                while(p2->right)//找到以p2为根的最右侧子节点
                {
                    p2=p2->right;
                }
                if(p2->right==nullptr)//若p2->right为空指针,则将p2的右节点指向p1,建立连接
                {
                    p2->right=p1;//建立连接
                    temp.push_back(p1->val);//趁机将p1节点的值加入temp容器
                    p1=p1->left;//p1左移
                    continue;//建立该节点好连接后,返回至p1位移后的位置进行其他点的连接
                }
            }
            else//p2是空指针
            {

            }
        }
    }
};

当连接过程进行到最后一个节点时,p1->right指向p1的根节点,p2为空指针,连接已经建好,接下来该进行断开连接的操作了。

(2)断开连接

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> temp;
        if(root==nullptr)
        {
            return temp;
        }
        TreeNode* p1 = root, * p2 = nullptr;//分别用2个指针记录节点位置

        //第一步,建立连接
        while()//循环条件暂且不知
        {
            p2=p1->left;//若是p1沿着连接返回自身的根节点处后,则下面需要p2置空操作,以防止无限循环
            if(p2)//p2不为空指针
            {
                while(p2->right && p2->right != p1)//找到以p2为根的最右侧子节点
                                //断开连接操作时,因为所有节点已经连好,所以这步操作需要添加条件
                {
                    p2=p2->right;
                }
                if(p2->right==nullptr)//若p2->right为空指针,则将p2的右节点指向p1,建立连接
                {
                    p2->right=p1;//建立连接
                    temp.push_back(p1->val);//趁机将p1节点的值加入temp容器
                    p1=p1->left;//p1左移
                    continue;//建立该节点好连接后,返回至p1位移后的位置进行其他点的连接
                }
                else//只有p2->right == p1这种情况了
                {
                    p2->right=nullptr;//将p2置空,置空后,明显应将p1指向右子节点,而外层else时有同样操作,优化下重复代码
                }
            }
            else//p2是空指针,表示所有连接已经连好,该进行断开连接的流程了
            {
                temp.push_back(p1->val);//趁机将p1节点的值加入temp容器
            }
            p1=p1->right;
        }
    }
};

当断开最后一个有连接的最右侧节点后,因为最后是p1=p1->right;操作,故p1为空指针,从这里不难得出外层while循环的进行条件是p1 != nullptr;

(3)最终代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> temp;
        if(root==nullptr)
        {
            return temp;
        }
        TreeNode* p1 = root, * p2 = nullptr;//分别用2个指针记录节点位置

        //第一步,建立连接
        while(p1)//p1不为空才进行下列代码
        {
            p2=p1->left;//若是p1沿着连接返回自身的根节点处后,则下面需要p2置空操作,以防止无限循环
            if(p2)//p2不为空指针
            {
                while(p2->right && p2->right != p1)//找到以p2为根的最右侧子节点
                                //断开连接操作时,因为所有节点已经连好,所以这步操作需要添加条件
                {
                    p2=p2->right;
                }
                if(p2->right==nullptr)//若p2->right为空指针,则将p2的右节点指向p1,建立连接
                {
                    p2->right=p1;//建立连接
                    temp.push_back(p1->val);//趁机将p1节点的值加入temp容器
                    p1=p1->left;//p1左移
                    continue;//建立该节点好连接后,返回至p1位移后的位置进行其他点的连接
                }
                else//只有p2->right == p1这种情况了
                {
                    p2->right=nullptr;//将p2置空,置空后,明显应将p1指向右子节点,而外层else时有同样操作,优化下
                }
            }
            else//p2是空指针,表示所有连接已经连好,该进行断开连接的流程了
            {
                temp.push_back(p1->val);//趁机将p1节点的值加入temp容器
            }
            p1=p1->right;
        }
        return temp;
    }
};

建议多常识写几次,加深印象。

(4)错误笔记

  • ==与=不要写错,否则会爆栈;

  • 注意指针相关操作;

  • 出错时,先自己浏览整个流程,尝试找出错因所在点,如果长时间未找出错因,建议在编译器上调试;

欢迎探讨!

猜你喜欢

转载自blog.csdn.net/qq_74066630/article/details/128809447