Morris遍历
概述
在二叉树遍历的深度优先遍历中,分析了如何用递归和非递归方式实现遍历二叉树,但是那些方法都无法做到空间复杂度为 O(1)
,对于递归方法,遍历时用到了函数栈,而对于非递归方法,则是直接申请了栈,这两种方法的空间复杂度均与树的高度相关,设树的高度为 h
,则空间复杂度为 O(h)
。事实上对于二叉树的遍历,还有一种空间复杂度为 O(1)
的方法,这就是——Morris遍历!!!
普通的递归和非递归解法在遍历过程中,在处理完某个节点后都是通过栈结构(函数栈或申请的栈)才可以返回上层去,正是二叉树的结构(只有从父节点指向孩子节点的指针,而没有从孩子节点指向父节点的指针)导致了从下层回到上层会如此困难。而Morris遍历的实质就是避免使用栈结构实现从下层返回上层,而是使下层到上层有指针,根据下层到上层的指针实现从下层到上层的移动。那么怎么才能有下层到上层的指针呢?
二叉树上的很多节点都有大量的空闲指针(如叶节点就有两个空闲指针),比如某些节点没有右孩子节点,那么这个节点的 right
指针就指向 null
,我们将之称为空闲状态,而Morris遍历就是使用了这些空闲指针。
Morris遍历
先不管先序遍历、中序遍历、后序遍历的相关概念,我们先看一下Morris遍历的过程:
设当前节点为 cur
,初始 cur = head
,则 cur
按照以下规则移动:
- 1.若
cur == null
,则过程停止,否则继续下面的过程; - 2.若
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
指针指向空,然后令cur = cur.right
。
- 1) 若
假设有如下的二叉树:
则根据上面的Morris遍历规则,其遍历如下:
(1)初始状态;
(2)cur
来到节点 4
,cur
此时有左子树,则找到其左子树的最右节点(即节点 3
),发现节点 3
的右指针指向 null
,那么让其指向 cur
,然后 cur = cur.left
;
(3)cur
来到节点 2
,cur
此时有左子树,则找到其左子树的最右节点(即节点 1
),发现节点 1
的右指针指向 null
,那么让其指向 cur
,然后 cur = cur.left
;
(4)cur
来到节点 1
,cur
此时没有左子树,则令 cur = cur.right
;
(5)cur
来到节点 2
,cur
此时有左子树,则找到其左子树的最右节点(即节点 1
),发现节点 1
的右指针指向 cur
,则令节点 1
的右指针指向 null
,然后令 cur = cur.right
;
(6)cur
来到节点 3
,cur
此时没有左子树,则令 cur = cur.right
;
(7)cur
来到节点 4
,cur
此时有左子树,则找到其左子树的最右节点(即节点 3
),发现节点 3
的右指针指向 cur
,则令节点 3
的右指针指向 null
,然后令 cur = cur.right
;
(8)cur
来到节点 6
,cur
此时有左子树,则找到其左子树的最右节点(即节点 5
),发现节点 5
的右指针指向 null
,那么让其指向 cur
,然后 cur = cur.left
;
(9)cur
来到节点 5
,cur
此时没有左子树,则令 cur = cur.right
;
(10)cur
来到节点 6
,cur
此时有左子树,则找到其左子树的最右节点(即节点 5
),发现节点 5
的右指针指向 cur
,则令节点 5
的右指针指向 null
,然后令 cur = cur.right
;
(11)cur