Morris算法实现二叉树的遍历

Morris算法实现二叉树的遍历

我们知道,实现二叉树的前序(preorder)、中序(inorder)、后序(postorder)。遍历有两个常用的方法,一是递归 (recursive),二是栈 (stack+iterative)。这两种方法都是 O(n) 的空间复杂度。
而 Morris Traversal 只需要 O(1) 的空间复杂度。这种算法跟线索二叉树很像,不过 MorrisTraversal 一边建线索,一边访问数据,访问完后销毁线索,保持二叉树不变。
一、先序遍历
步骤:

  1. 初始化当前节点 cur 为 root 节点
  2. 如果 cur 没有左孩子,则输出当前节点并将其右孩子作为当前节点,即 cur = cur->rchild。
  3. 如果 cur 有左孩子,则寻找 cur 的前驱,即 cur 的左子树的最右下角结点。
    a) 如果前驱节点的右孩子为空,将它的右孩子指向当前节点,输出当前节点(在这里输出,这是与中序遍历唯一的不同点),当前节点更新为当前节点的左孩子。
    b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状),当前节点更新为当前节点的右孩子。
  4. 重复 2、3 步骤,直到 cur 为空。

如图所示:当前节点为1(cur),有左孩子,则找前驱,即cur的左子树的最右下角的结点,遍历到6的位置,该结点右孩子为空,那么右孩子(即右孩子的指针指向)指向cur,可看到图中虚线,当前结点更新为cur的左孩子,即2的位置,后序操作类似,可根据图按照原理推导
`

void pre_order_morris(bt_node_t *root) 
{
	bt_node_t *cur;
	cur = root;
	while (cur != NULL) 
	{
		if (cur->left == NULL ) 
		{
			cout<<cur->elem<<endl; //输出结点值
			cur = cur->right;
		}
		else
		{
			/* 查找前驱 */
			bt_node_t *node = cur->left;
			while (node->right != NULL && node->right != cur)
				node = node->right;
			if (node->right == NULL ) 
			{ 
				/* 还没线索化,则建立线索 */
				cout<<cur->elem<<endl; //输出结点值 /* 仅这一行的位置与中序不同 */
				node->right = cur;
				cur = cur->left;
			} 
			else
			{ 
				/* 已经线索化,则删除线索 */
				node->right = NULL;
				/* prev = cur; 不能有这句,cur 已经被访问 */
				cur = cur->right;
			} 
		}
	 } 
}

二、中序遍历
步骤:

  1. 初始化当前节点 cur 为 root 节点
  2. 如果 cur 没有左孩子,则输出当前节点并将其右孩子作为当前节点,即 cur = cur->rchild。
  3. 如果 cur 有左孩子,则寻找 cur 的前驱,即 cur 的左子树的最右下角结点。
    a) 如果前驱节点的右孩子为空,将它的右孩子指向当前节点,当前节点更新为当前节点的左孩子。
    b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状),输出当前节点,当前节点更新为当前节点的右孩子。
  4. 重复 2、3 步骤,直到 cur 为空。

如图所示,中序遍历和先序遍历基本原理差不多,大家可根据先序遍历的方法理解
在这里插入图片描述

void in_order_morris(bt_node_t *root)
{
	bt_node_t *cur;
	cur = root;
	while (cur != NULL ) 
	{
		//没有左孩子
		if (cur->left == NULL ) 
		{
			cout<<cur->elem;  //输出结点的值
			cur = cur->right;  
		} 
		else
		{
			/* 查找前驱 */
			bt_node_t *node = cur->left;
			while (node->right != NULL && node->right != cur)
				node = node->right;
			if (node->right == NULL )
			{ 
				/* 还没线索化,则建立线索 */
				node->right = cur;
				cur = cur->left;
			}
			else 
			{ 
				/* 已经线索化,则访问节点,并删除线索 */
				cout<<cur->elem<<endl; //输出结点值
				node->right = NULL;
				prev = cur;
				cur = cur->right;
			}
		 }
	 }
 }

三、后序遍历
步骤:

  1. 初始化当前节点 cur 为 root 节点
  2. 如果 cur 没有左孩子,则将其右孩子作为当前节点,即 cur = cur->rchild。
  3. 如果 cur 有左孩子,则寻找 cur 的前驱,即 cur 的左子树的最右下角点。
    a) 如果前驱节点的右孩子为空,将它的右孩子指向当前节点,当前节点更新为当前节点的左孩子。
    b) 如果前驱节点的右孩子为当前节点,将它的右孩子重新设为空(恢复树的形状),倒序输出从当前节点的左孩子到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右孩子。
  4. 重复 2、3 步骤,直到 cur 为空。

后序遍历和前面有点不同,增加一个临时结点temp,并令左孩子指向根节点,如图结点0位temp,大致分析方法一致的,在图中第十个树中,from和to结点不同,那么需要倒序输出结点的值,具体可参照代码
在这里插入图片描述

void post_order_morris(bt_node_t *root)
{
	bt_node_t dummy = { 0, NULL, NULL };
	bt_node_t *cur, *prev = NULL;
	dummy.left = root;
	cur = &dummy;
	while (cur != NULL )
	{
		if (cur->left == NULL )
		{
			prev = cur; /* 必须要有 */
			cur = cur->right;
		} 
		else 
		{
			bt_node_t *node = cur->left;
			while (node->right != NULL && node->right != cur)
				node = node->right;
			if (node->right == NULL ) 
			{ 
				/* 还没线索化,则建立线索 */
				node->right = cur;
				prev = cur; /* 必须要有 */
				cur = cur->left;
			} 
			else 
			{ 
				/* 已经线索化,则访问节点,并删除线索 */
				visit_reverse(cur->left, prev); // call print
				prev->right = NULL;
				prev = cur; /* 必须要有 */
				cur = cur->right;
			} 
		} 
	} 
}

//逆序
static void reverse(bt_node_t *from, bt_node_t *to)
{
	bt_node_t *x = from, *y = from->right, *z;
	if (from == to) return;
	while (x != to)
	{
		z = y->right;
		y->right = x;
		x = y;
		y = z;
	} 
}
static void visit_reverse(bt_node_t* from, bt_node_t *to) 
{
	bt_node_t *p = to;
	//逆序输出
	reverse(from, to);
	while (1)
	{
		cout<<p->elem<<endl;  //输出结点值
		if (p == from)
			break;
		p = p->right;
	}
	//逆序之后再还原原来的顺序
	reverse(to, from);
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值