Morris遍历
Morris遍历就是用来遍历二叉树的一种算法,并且能够满足时间复杂度为O(N),额外空间复杂度为O(1)。算法的大致流程如下:使用一个指针cur,从二叉树的根节点出发遍历二叉树。直至cur指向空。
判断cur是否有左子树:
- 若没有左子树,直接让cur往右走(cur = cur->right)
- 若有左子树,则找到左子树的最右结点。对最右结点进行判断
- 若最右结点的右孩子为空,则让最右结点的右孩子指向cur。然后让cur往左走(cur = cur->left).
- 若最右节点的右孩子为cur结点(因为我们有可能对右孩子改变过),则让最右结点的右孩子为空。最后让cur向右走。
以下图为例:
从1开始遍历,cur指向1。1有左子树,则找到左子树的最右结点5,5的右孩子为空,则将5的右孩子指向cur指向1。然后cur向左走,cur指向2。
2有左子树,则找到左子树的最右节点4,4的右孩子为空,则将4的右孩子指向cur指向2。然后cur向左走,cur指向4.
4没有左子树,则cur向右走,此时cur指向2。2有左子树,则找到左子树的最右节点4,4此时的右孩子因为被修改过指向2,因此此时让4的右孩子为空,让cur向右走,cur向5.5没有左孩子,cur向右走,因为5的右孩子之前被改为1,因此cur指向1。1有左子树,则找到左子树的最右结点5,5的右孩子为1,因此此时让5的右孩子为空,让cur向右走,cur指向3。依次类推,cur接下来依次走过:6、3、7。
故整个Morris遍历二叉树的顺序为:1 2 4 2 5 1 3 6 3 7;
由此可见,有左子树的结点出现两次,没有左子树的结点出现了一次。
程序示例:
void MorrisResearch(Node *root)
{
if (root == NULL)return;
Node *cur = root;//设置当前指针
Node *mostRight = NULL;//设置指向左子树的右节点指针
while (cur!=NULL)//完成所有树节点的遍历
{
mostRight = cur->left;//找到当前结点的左孩子树的头结点
if (cur->left != NULL)
{//当当前结点有左孩子时
while (mostRight->right!=NULL && mostRight->right!=cur)
{
//当前结点左子树的右节点不为空且不为当前结点时
//一值向右遍历,找到当前结点左子树的最右结点
mostRight = mostRight->right;
}
if (mostRight->right == NULL)
{
//若mostRight的右指针指向空,让其指向cur,并且cur向左移动
mostRight->right = cur;
cur = cur->left;
continue;//同时跳出当前循环
}
else
{
//否则,满足mostRight指向cur
mostRight->right = NULL;
}
}
//当前结点没有左孩子时,当前结点向右移动
cur = cur->right;
}
}
Morris遍历实现二叉树先中后序遍历
那么如何利用Morris来实现先中后序遍历呢?如下:
先序遍历
打印第一次出现的结点数据
void Morris_pre(Node*root)
{
//先序遍历只打印结点第一次出现时数据
if (root == NULL)return;
Node*cur = root;
Node*mostRight = NULL;
while (cur!=NULL)
{
mostRight = cur->left;
if (mostRight!= NULL)
{
while (mostRight->right!=NULL && mostRight->right !=cur)
{
mostRight = mostRight->right;
}
if (mostRight->right == NULL)
{
mostRight->right = cur;
cout << cur->data << endl;//打印出现过两次的第一次数据
cur = cur->left;
continue;
}
else
{
mostRight->right = NULL;
}
}
else
{
cout << cur->data << endl;//打印只出现过一次的数据
}
cur = cur->right;
}
}
中序遍历
出现一次的结点直接打印数据,出现两次的结点打印第二次出现时的数据
void Morris_mid(Node*root)
{
//中序遍历
//当只有一个morris遍历只有一个数时直接打印,有两个数时只打印第二个
if (root == NULL)return;
Node*cur = root;
Node*mostRight = NULL;
while (cur != NULL)
{
mostRight = cur->left;
if (mostRight != NULL)
{
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;
}
}
cout << cur->data << endl;//打印出现过一次的数据及出现过两次的第二次出现的数据
cur = cur->right;
}
}
后序遍历
只出现过一次的结点直接跳过,当遍历至出现两次的结点的第二次时,逆序打印该结点的右边界的所有结点。遍历完后打印根节点的右边界的所有结点。
void reversePrint(Node *node)
{
//首先将右边界逆转
Node *pre = NULL;
Node *next = NULL;
while (node!=NULL)
{
next = node->right;
node->right = pre;
pre = node;
node = next;
}
//打印
Node*cur = pre;
while (cur != NULL)
{
cout << cur->data << endl;
cur = cur->right;
}
//再将右边界逆转回来
node = pre;
pre = NULL;
next = NULL;
while (node != NULL)
{
next = node->right;
node->right = pre;
pre = node;
node = next;
}
node = pre;
}
void Morris_back(Node*root)
{
//后序遍历
//第一次遇到不管,第二次遇到逆序打印当前结点左数的全部右边界,最后遍历完后,逆序打印整个树的右边界
if (root == NULL)return;
Node*cur = root;
Node*mostRight = NULL;
while (cur != NULL)
{
mostRight = cur->left;
if (mostRight != NULL)
{
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;
reversePrint(cur->left);//第二次遇到逆序打印当前结点左数的全部右边界
}
}
cur = cur->right;
}
reversePrint(root);//最后打印整个树的右边界
}