算法学习(23):Morris遍历

Morris遍历

什么是Morris遍历

Morris遍历是一种二叉树的遍历算法,它可以做到时间复杂度O(N),额外空间复杂度O(1)。它的做法是通过使用叶节点的空指针来完成遍历,除了几个变量之外,不用额外申请空间,从而做到额外空间复杂度O(1)

Morris遍历流程

假设来到当前节点cur,开始时cur来到头节点位置

  1. 如果cur没有左孩子,cur向右移动(cur = cur.right)
  2. 如果cur有左孩子,找到左子树上最右的节点mostRight:
    a.如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)
    b.如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)
  3. cur为空时遍历停止

在Morris遍历中,有左子树的节点会到达两次,其他节点都只到达一次。关于Morris遍历实现先序、中序、后序遍历,下面只说怎么改,至于这么改的原因详见https://www.bilibili.com/video/BV13g41157hK?p=15&vd_source=77d06bb648c4cce91c6939baa0595bcd P15 01:10:30

Morris遍历C++代码实现

void Morris(TreeNode* head)
{
    if (head == NULL)
    {
        return;
    }
    TreeNode* cur = head;
    TreeNode* mostRight = NULL;
    while (cur != NULL)
    {
        if (cur->left != NULL)
        {
            mostRight = cur->left;
            while (mostRight->right != NULL && mostRight->right != cur)  //不越界并且不碰上cur
            {
                mostRight = mostRight->right;
            }
            if (mostRight->right == NULL)
            {
                mostRight->right = cur;
                cur = cur->left;
                continue;     //跳到下一次循环,不然会触发往右走的语句
            }
            else
            {
                mostRight->right = NULL;
            }
        }
        cur = cur->right;
    }
}

Morris遍历实现先序遍历

所有可以到达两次的节点只在第一次到达时打印,其他没有左子树的节点直接打印

void preOrderMorris(TreeNode* head)
{
    if (head == NULL)
    {
        return;
    }
    TreeNode* cur = head;
    TreeNode* mostRight = NULL;
    while (cur != NULL)
    {
        if (cur->left != NULL)
        {
            mostRight = cur->left;
            while (mostRight->right != NULL && mostRight->right != cur) 
            {
                mostRight = mostRight->right;
            }
            if (mostRight->right == NULL)
            {
                printTreeNode(cur);   //第一次到达的打印时机
                mostRight->right = cur;
                cur = cur->left;
                continue;
            }
            else
            {
                mostRight->right = NULL;
            }
        }
        else
        {
            printTreeNode(cur);     //没有左子树的打印时机
        }
        cur = cur->right;
    }
}

Morris遍历实现中序遍历

所有可以到达两次的节点只在第二次到达时打印,其他没有左子树的节点直接打印

void inOrderMorris(TreeNode* head)
{
    if (head == NULL)
    {
        return;
    }
    TreeNode* cur = head;
    TreeNode* mostRight = NULL;
    while (cur != NULL)
    {
        if (cur->left != NULL)
        {
            mostRight = cur->left;
            while (mostRight->right != NULL && mostRight->right != cur)
            {
                mostRight = mostRight->right;
            }
            if (mostRight->right == NULL)
            {
                mostRight->right = cur;
                cur = cur->left;
                continue;
            }
            else
            {
                mostRight->right = NULL;
            }
        }
        printTreeNode(cur);   //有左子树第二次到达和没有左子树第一次到达打印时机
        cur = cur->right;
    }
}

Morris遍历实现后序遍历

在遍历时的打印时机只放在有左子树的节点在第二次到达时自己时,逆序打印它左子树的右边界。当cur为空时,打印整棵树的右边界
如何打印右边界,单链表双指针逆序,打印完再调用一次逆序,还原回来。详细带图讲解见https://www.bilibili.com/video/BV13g41157hK?p=15&vd_source=77d06bb648c4cce91c6939baa0595bcd P15 01:19:00

TreeNode* reverseEdge(TreeNode* head);

void printEdge(TreeNode* node);

void postOrderMorris(TreeNode* head)
{
    if (head == NULL)
    {
        return;
    }
    TreeNode* cur = head;
    TreeNode* mostRight = NULL;
    while (cur != NULL)
    {
        if (cur->left != NULL)
        {
            mostRight = cur->left;
            while (mostRight->right != NULL && mostRight->right != cur)
            {
                mostRight = mostRight->right;
            }
            if (mostRight->right == NULL)
            {
                mostRight->right = cur;
                cur = cur->left;
                continue;
            }
            else
            {
                mostRight->right = NULL;
                printEdge(cur->left);     //有左子树的节点第二次到达时它自己时打印左子树的右边界
            }
        }
        cur = cur->right;
    }
    printEdge(head);    //打印整棵树的右边界
}

void printEdge(TreeNode* node)
{
    TreeNode* tail = reverseEdge(node);
    TreeNode* cur = tail;
    while (cur != NULL)
    {
        cout << cur->val << " ";
    }
    reverseEdge(tail);
}

TreeNode* reverseEdge(TreeNode* head)
{
    TreeNode* cur = head;
    TreeNode* next = NULL;
    TreeNode* pre = NULL;
    while (cur->right != NULL)
    {
        next = cur->right;
        cur->right = pre;
        pre = cur;
        cur = next;
    }
    return pre;
}

Morris遍历的应用

判断一棵树是否是二叉搜索树(中序遍历是递增的)

bool isBST(TreeNode* head)
{
    if (head == NULL)
    {
        return true;
    }
    TreeNode* cur = head;
    TreeNode* mostRight = NULL;
    int pre = INT_MIN;    //上一个遍历到的节点的值
    while (cur != NULL)
    {
        if (cur->left != NULL)
        {
            mostRight = cur->left;
            while (mostRight->right != NULL && mostRight->right != cur)
            {
                mostRight = mostRight->right;
            }
            if (mostRight->right == NULL)
            {
                mostRight->right = cur;
                cur = cur->left;
                continue;
            }
            else
            {
                mostRight->right = NULL;
            }
        }
        if (cur->val <= pre)    //判断是否是递增的,不是则返回false
        {
            return false;
        }
        cur = cur->right;
    }
    return true;
}
posted @ 2022-08-03 09:49  小肉包i  阅读(158)  评论(0)    收藏  举报