面试官:你能将遍历二叉树进一步优化到空间复杂度为O(1)吗?

我们知道,在遍历二叉树的时候,不管是用递归的方法,还是用非递归的方法,都无法做到额外空间复杂度为O(1)。这是因为遍历二叉树的递归方法实际上使用了函数栈,非递归的方法使用了申请的栈,两者的额外空间都与树的高度有关,所以空间复杂度为O(H),H为二叉树的高度。

那么怎么做到将空间复杂度优化到O(1)呢?这就要用到大名鼎鼎的Morris遍历。

一、什么是Morris遍历

Morris遍历是二叉树遍历算法的超强进阶算法,与递归、非递归(栈实现)的空间复杂度比较,Morris遍历可以将非递归遍历中的空间复杂度降为O(1)。从而实现时间复杂度为O(N),而空间复杂度为O(1)的精妙算法。

我们知道,二叉树中有一些结点没有左孩子或右孩子,甚至左右孩子都没有。它们会有指向NULL的指针,我们将其称为空闲指针。Morris遍历就是利用了这些空闲指针,实现空间开销的极限缩减

二、Morris遍历的实质是什么

首先来看普通的递归和非递归解法,其实就是使用了栈结构,在处理完二叉树某个节点后可以回到上层去。为什么从下层回到上层会如此难呢?因为我们知道,二叉树的结构,每个节点都有指向孩子节点的指针,所以从上层到下层容易,但是没有指向父节点的指针,所以从下层到上层需要借助栈结构来完成。

而Morris遍历能将空间复杂度优化到O(1),就是避免了使用栈结构,让下层到上层有指针,具体是通过让底层节点指向NULL的空闲指针指回上层的某个节点,从而完成下层到上层的移动。

三、Morris遍历的过程

现在暂且不管是先序、中序还是后序遍历,先看看Morris遍历的过程。

假设当前节点为cur,初始时cur就是整棵树的根结点,根据以下标准让cur移动:

1、如果cur为NULL,则过程停止,否则继续下面的过程。
2、如果cur没有左子树,则让cur向右移动,即令cur=cur->right。
3、如果cur有左子树,则找到cur左子树上最右的节点,记为mostRight。
  1) 如果mostRight->right == NULL,则令mostRight->right = cur,也就是让mostRight的right指针指向当前节点,然后让cur向左移动,即令cur = cur->left。
  2) 如果mostRight->right == cur,则令mostRight->right = NULL,也就是让mostRight的right指针指向NULL,然后让cur向右移动,即令cur= cur->right。

以上就是Morris遍历的过程,看完可能会被绕的云里雾里的,没关系,下面通过一个例子,我们一步一步来看。

假设一颗二叉树如下图所示:
在这里插入图片描述
1、初始时,cur来到节点4,cur此时有左子树,所以根据刚才介绍的标准,找到cur的左子树中最右的节点(即节点3),发现节点3的右指针是指向NULL的,那么让其指向cur,树被调整成如下图所示,然后cur = cur->left,即cur向左移动来到节点2。
在这里插入图片描述
2、cur来到节点2,cur此时有左子树,找到cur的左子树中最右的节点(即节点1),发现节点1的右指针指向NULL,那么让其指向cur,树被调整成如下图所示的样子,然后cur继续向左移动,来到节点1。
在这里插入图片描述
3、cur来到节点1,此时没有左子树,根据标准令cur向右指针方向移动,所以cur回到节点2。(此时,第二次到达节点2)

4、cur来到节点2,cur此时有左子树,找到cur的左子树中最右的节点,即节点1,发现节点1的右指针是指向cur的,根据标准让其指向NULL,树被调整回如下图所示的样子,然后根据标准,cur向右指针方向移动,所以cur来到了节点3。
在这里插入图片描述
5、cur来到节点3,cur此时没有左子树,根据标准,令cur向右指针方向移动,所以cur回到节点4。(此时,第二次到达节点4)

6、cur来到节点4,cur此时有左子树,找到cur的左子树中最右节点,即节点3,发现节点3的右指针此时指向cur,那么现在让其指向NULL,树被调整回如下图所示的样子,然后根据标准,cur向右移动来到节点6。
在这里插入图片描述
7、cur来到节点6,cur此时有左子树,找到cur左子树中最右的节点,即节点5,发现节点5的右指针此刻是指向NULL的,那么现在让其指向cur,树被调整成如下图所示。然后根据标准,cur向左移动来到节点5。
在这里插入图片描述
8、cur来到节点5,cur此时没有左子树,根据标准令cur向右指针方向移动,所以cur回到了节点6。(此时第二次到达节点6)

9、cur来到节点6,cur此时有左子树,找到cur的左子树中最右的节点,即节点5,发现节点5的右指针此刻是指向cur的,那么现在让其指向NULL,树被调整回如下图所示的样子,然后根据标准,cur向右移动来到节点7。
在这里插入图片描述

10、cur来到节点7,cur此时没有左子树,根据标准,令cur向右指针方向移动,cur来到NULL的位置。

11、cur为空,过程停止。

经过上面的步骤后,会产生Morris序:4、2、1、2、3、4、6、5、6、7。可以看出,在一颗二叉树中,对于有左子树的节点都可以到达两次(4、2、6),对于没有左子树的节点都只会到达一次。

代码实现如下:

void morris(TreeNode* root){
   
	if(!root)	return ;
	TreeNode* cur = root;
	TreeNode* mostRight = NULL;
	while(cur != NULL){
   
		
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值