二叉查找树实现(五)——Morris遍历算法

     前面已经关于二叉树遍历,已经学习了递归遍历以及非递归遍历,其中非递归遍历需要直接用到栈,空间复杂度O(n),递归遍历由于用到递归,因此也需要调用栈,空间复杂度同样是O(n),那么有没有一种遍历算法的空间复杂度是O(1)呢?答案就是Morris算法。

      Morris算法跟线索树有一点相似,都是通过利用数中空的指针域来方便遍历,但是Morris算法不需要提前对树遍历一边以建立线索,也不需要用标志位successor(间本系列四线索树)来标注后继节点。唯一的缺点就是有些节点需要访问两次。接下来我们看一下Morris算法的原理。

   以下部分引自https://www.jianshu.com/p/484f587c967c这篇文章是目前见到讲Morris算法讲的最详细最具体的

     Morris遍历算法的步骤如下:

1, 根据当前节点,找到其前序节点,如果前序节点的右孩子是空,那么把前序节点的右孩子指向当前节点,然后进入当前节点的左孩子。

2, 如果当前节点的左孩子为空,打印当前节点,然后进入右孩子。

3,如果当前节点的前序节点其右孩子指向了它本身,那么把前序节点的右孩子设置为空,打印当前节点,然后进入右孩子

     那么问题来了,如何找到节点的前序节点呢?前序节点是指在某个特定的遍历顺序中,应该在当前节点之前输出的那一个节点。以图中二叉树为例,如果规定采用中序遍历,那么1就是3的前序节点,7就是8的前序节点。

      找一个节点的前序节点的步骤如下:找该节点的左节点,如果找不到左节点,那么其父节点就是前序节点,如果有左节点,并且左节点的右节点为空,那么该左节点就是要找的前序节点,否则,一直取右节点,直至右节点为null时,才算找到了前序节点。以10为例,10并没有左节点,因此其前序节点就是其父节点8;以3为例,3的左节点是1,并且1的右节点为空,因此1是3的前序节点;以8为例,其左节点为3,3的的右节点不为null,那么一直取右节点取下去,直到取到7,7就是8的前序节点。

                                                               
                                                                                          图1
       我们对图1的树,执行上面Morris算法的1,2,3步骤:

   (1)首先是节点8,找到节点8的前序节点7,7的右孩子为空,所以将7的右孩子指向8,所以就有了图2,然后进入8的左孩子3

                                                          

                                                                                          图2

    (2)3的左节点不为空,那么继续操作,找到3的前序节点1,1的右孩子为空,因此将1的右孩子指向3,进入3的左孩子1,于是二叉树变为图3

                                                           

                                                                                          图3               

    (3)1的左节点为空,因此输出1,进入1的右孩子,也就是3

  (4)因为3的前序节点1的右孩子指向了3自身,因此,因此将1的右孩子设为空,二叉树变为图4,打印3,并且进入3的右孩子6

                                                       

                                                                                            图4 

(5)6的左孩子不为空,找到其前序节点4,4的右孩子为null,因此将4的右孩子指向6,二叉树变为图5,同时进入6的左孩子4

                                                                                                                     

                                                                                        图5 

(6)4的左孩子为null,输出4,并且进入其右孩子6,

(7)因为6的前序节点的右孩子4指向了6自身,因此打印6,同时将4的右孩子置为null,二叉树变为图6,进入6的右孩子7

                                                       

                                                                                         图6 

(8)7的左孩子为null,因此输出7,因此输出7,并进入7的右孩子8

(9)8的前序节点为7,并且7的右孩子指向了8本身,因此将7的右孩子置为null,二叉树变为图7,同时打印8,并进入8的右孩子10

                                                                                                             

                                                                                       图7 

(10)接下来的操作如出一辙,继续下去可以遍历整棵树

      可以看见,在使用Morris遍历树的过程中曾经短暂地改变了树的结构,例如图2与图1比较,图3与图2比较,但是在,每一次访问完根节点(包括子树的节点后),又会把这种改变擦除,例如图4与图3比较,图6与图6比较。整个树遍历完成之后,树会恢复到最开始的样子,图7就是最初始的样子。

  整个算法的程序如下:

void Btree::MorriisVisit()
{
	MorrisVisit(root);
}
void Btree::MorrisVisit(BtreeNode *mvp)
{
	BtreeNode *p=mvp,*tmp;
	while (p!=0)
	if (p->left == 0) //如果左节点为null,打印当前节点并且转入右孩子
	{
		cout<<p->ele<<' ';
		p = p->right;
	 }
	else      //如果左节点不为空,我们要分两种情况,第一种时之前已经建立起前序节点与当前节点的联系,那么要拆除这种联系并且输出当前节点,再转入右节点,第二种情况是还没有建立起连接,那么就要建立其当前的连接,然后转入左孩子
	{
		tmp = p->left;
		while (tmp->right != 0 && tmp->right != p) //找前序节点
			tmp = tmp->right;
		if (tmp->right == 0)  //判断,属于第二种情况
		{
			tmp->right = p;
			p = p->left;
		}

		else   //判断,属于第一种情况
		{
			cout << p->ele << ' ';
			tmp->right = 0;
			p = p->right;
		}
	}
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值