算法学习(23):Morris遍历
Morris遍历
什么是Morris遍历
Morris遍历是一种二叉树的遍历算法,它可以做到时间复杂度O(N),额外空间复杂度O(1)。它的做法是通过使用叶节点的空指针来完成遍历,除了几个变量之外,不用额外申请空间,从而做到额外空间复杂度O(1)
Morris遍历流程
假设来到当前节点cur,开始时cur来到头节点位置
- 如果cur没有左孩子,cur向右移动(cur = cur.right)
- 如果cur有左孩子,找到左子树上最右的节点mostRight:
a.如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)
b.如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right) - 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;
}
浙公网安备 33010602011771号