1.morris traversal
莫里斯遍历,是在O(n)时间复杂度和O(1)空间复杂度下实现的二叉树遍历,带有一定的线索二叉树思想
其根本思想是借助【线索】来替代一般递归写法产生的栈结构,把节点信息存在了【线索】中,从而避免了使用栈带来的空间开销。这也是莫里斯遍历和一般遍历写法【在本质上的唯一区别】
用一句话概括morris traversal就是,
建好线索再行遍历
2.莫里斯遍历剖析与模板总结
以前序遍历为例,对于一般的前序遍历递归写法,我们有
class Solution:
def preorderTraversal(self, root: TreeNode):
if not root:
return
visit root
self.preorderTraversal(root.left)
self.preorderTraversal(root.right)
上面代码中,进入self.preorderTraversal(root.left)前,root会压入递归栈保存,等self.preorderTraversal(root.left)返回时root从递归栈弹出;然后在当前函数获取root.right,并执行self.preorderTraversal(root.right)
对于root(如下图结点1)而言,当它的左子树root.left被访问完毕后,也就是左子树root.left的最右端结点mostright(如下图结点4)被访问后,需要从mostright回到root,以顺利获得root.right。这个回到root的过程是通过递归栈不断出栈结点实现的,包括非递归的前序遍历写法也只是主动用栈模拟了这个过程而已
2.1 建立线索
如果我们能在4和1直接建立一条【线索】,如上图,不就避免了栈实现了吗?
因此,对于当前结点root:
- step1. 建立mostright->root线索。对于root 1,在访问左子树root.left 2之前,我们先找到root的前序结点 4并且建立线索4->1。这样我们就可以保证在访问完左子树root.left后能够顺着线索回到root。线索建立完后,才可以考虑访问root.left
- step2. 访问左子树root.left。对于root.left的每个结点,重复step1。如果不存在左子树root.left或者从左子树的mostright回到root,那么就访问右子树root.right
- step3. 访问右子树root.right。对于root.right的每个结点,重复step1 2
2.2 删除线索
但是,我们建好线索之后,就会形成环路,如果不及时删除线索,
- 既会导致树结构的改变
- 又会导致程序一直在环内跑产生死循环
因此,必须考虑及时删除不需要的线索,恢复树的正常结构。我们知道,程序从左子树的mostright回到root之后,mostright->root线索就完成了使命不再需要了,应该删除
综上所述,莫里斯遍历的整体思路是
- step1. 建立mostright->root线索。对于root 1,在访问左子树root.left 2之前,我们先找到root的前序结点 4并且建立线索4->1。这样我们就可以保证在访问完左子树root.left后能够顺着线索回到root。线索建立完后,才可以考虑访问root.left
- step2. 访问左子树root.left。对于root.left的每个结点,重复step1。如果不存在左子树root.left或者从左子树的mostright回到root,说明左子树访问完毕,那么就删除对应线索,回到root
- step3. 访问右子树root.right。对于root.right的每个结点,重复step1 2
上面的整个过程和递归长得太像了,唯二不同就在于先建好当前结点root与其前序结点mostright的线索,再开始访问左子树和右子树,并且从左子树回到root时,删除对应的mostright->root线索。先建后删,不过如此
2.3 莫里斯遍历模板
核心!!!重点!!!从1到2.3弄懂后面水到渠成。看这多么优美的模板
def morrisTraversalModel(root) -> None:
while root:
# 1.root有左子树,就要建立mostright->root的线索
if root.left:
mostright = root.left
while mostright.right and mostright.right != root:
mostright = mostright.right
# mostright to root没有线索
if not mostright.right:
# 建立线索
mostright.right = root
# 线索建好了访问左子树
root = root.left
# 能够从mostright回到root,说明左子树访问完毕,要删除线索,然后访问右子树
else:
mostright.right = None
root = root.right
# 2.root没有左子树,那么访问右子树
else:
root = root.right
3.莫里斯前序遍历
基于2.3模板,控制一下访问结点的时机,很快就写出来了
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
res = []
while root:
# 1.root有左子树,就要建立mostright->root的线索
if root.left:
mostright = root.left
while mostright.right and mostright.right != root:
mostright = mostright.right
# mostright to root没有线索
if not mostright.right:
# 建立线索
mostright.right = root
### 访问自己 ###
res.append(root.val)
# 线索建好了访问左子树
root = root.left
# 能够从mostright回到root,要删除线索,然后访问右子树
else:
mostright.right = None
root = root.right
# 2.root没有左子树,那么访问右子树
else:
### 访问自己 ###
res.append(root.val)
root = root.right
return res
建线索总是第一步,这与访问结点的顺序无关,是独立的必须完成的第一步。建完后再考虑访问结点的时机,前序遍历就是在模板的基础上新增两处
### 访问自己 ###
res.append(root.val)
4.莫里斯中序遍历
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
while root:
# 1.root有左子树,就要建立mostright->root的线索
if root.left:
mostright = root.left
while mostright.right and mostright.right != root:
mostright = mostright.right
# mostright to root没有线索
if not mostright.right:
# 建立线索
mostright.right = root
# 线索建好了访问左子树
root = root.left
# 能够从mostright回到root,要删除线索,然后访问右子树
else:
mostright.right = None
### 访问自己 ###
res.append(root.val)
root = root.right
# 2.root没有左子树,那么访问右子树
else:
### 访问自己 ###
res.append(root.val)
root = root.right
return res
建线索总是第一步,这与访问结点的顺序无关,是独立的必须完成的第一步。建完后再考虑访问结点的时机,中序遍历也是在模板的基础上新增两处
### 访问自己 ###
res.append(root.val)
5.莫里斯后续遍历
由于后序遍历的实际栈操作相对复杂,因此转换为中右左的倒置,而中右左又是反序前序遍历,如此得解
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
res = []
while root:
# 1.root有右子树,就要建立mostleft->root的线索
if root.right:
mostleft = root.right
while mostleft.left and mostleft.left != root:
mostleft = mostleft.left
# mostleft to root没有线索
if not mostleft.left:
# 建立线索
mostleft.left = root
### 访问自己 ###
res.append(root.val)
# 线索建好了访问右子树
root = root.right
# 能够从mostleft回到root,要删除线索,然后访问左子树
else:
mostleft.left = None
root = root.left
# 2.root没有右子树,那么访问左子树
else:
### 访问自己 ###
res.append(root.val)
root = root.left
return res[::-1]