二叉树的Morris遍历

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);//最后打印整个树的右边界
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值