文章概览
前言
前面我们遇到了Morris中序遍历算法,它可以实现O(1)的空间复杂度和O(n)的时间复杂度。那么在后序遍历时可以使用该算法来降低对空间的使用吗?
幸运的是,确实存在Morris后序遍历算法,它和Moriis中序遍历算法相似,大家学习后可以自行比较两个算法之间的关联与区别。
下面我们结合Leetcode中第145题二叉树的后序遍历,来学习Morris后遍历算法。
以下使用python3~
一、Morris后序遍历算法详解
本部分参考leetcode145官方题解
后序遍历的顺序是左-右-中,在不使用递归的情况下,我们需要遍历完左子树可以再回到当前节点,因此仍然要像Morris中序遍历那样,找到当前节点的前驱节点。
找当前节点cur的前驱节点:(关键)
pre = cur.left
if pre:
while pre.right and pre.right!=cur:
pre = pre.right
# while循环结束后,得到的pre即为cur在中序遍历下的前驱节点
由于后序是右子树在根之前被遍历,我们就不能在遍历到某个节点时直接将其值放入输出序列中,而是要将当前节点的左子节点到其前驱节点的路径倒序放入结果中。
1. 具体算法
从根节点root出发
- 如果当前节点的左子节点为空,则遍历当前节点的右子节点;
- 如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点;
- 如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点,当前节点更新为当前节点的左子节点。
- 如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。倒序输出从当前节点的左子节点到该前驱节点这条路径上的所有节点。当前节点更新为当前节点的右子节点。
和Morris中序遍历的区别:
1、不能直接遍历当前节点的值,要调用函数获取当前节点之前的节点路径(因为后序中根节点最后访问)
2、当前驱节点的右子节点为当前节点时,一定要将它的右子节点重新设为空,才能进行后续操作,这样能保证正确获取路径。而在中序遍历中,不置空也可以。
其他部分都是一样的,大家对比两个算法的代码框架就能进一步理解了~
2. 代码实现
ans = []
def morris_postorder(root):
def add_path(cur):
global ans
# 倒序输出从当前节点的左子节点,到其前驱节点这条路径上的所有节点。
tmp = []
while cur:
tmp.append(cur.val)
cur = cur.right
ans += tmp[::-1]
cur = root
while cur:
pre = cur.left
if pre:
while pre.right and pre.right!=cur:
pre = pre.right
if not pre.right:
pre.right = cur
cur = cur.left
continue
else:
pre.right = None
add_path(cur.left)
cur = cur.right
add_path(root)
return ans
从程序中可以看出,当cur为空时就退出while循环了,此时cur只是走到了树的叶子最右端,但并没有加入从最右叶子到树根节点的这条路径。
想象一下当root节点的所有右子树节点都没有左子树时,cur可以一路向右走,当走到叶子节点的right时,cur变为空,退出循环。退出时还没来得及添加路径。
因此while结束后,再进行一次add_path(root)。
二、Leetcode 145题目描述
三、解题方法(×3)
1. 简单递归
解题思路
递归是最常用和最直观的遍历算法,后序遍历时,先递归左子树,再递归右子树,最后遍历当前节点的值。
时间复杂度O(n),空间复杂度O(n)
完整代码
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
self.res = []
def postorder(root):
if not root: return
postorder(root.left)
postorder(root.right)
self.res += [root.val]
postorder(root)
return self.res
2. 利用栈的迭代
解题思路
递归算法得以实现的原因是其内部使用了栈,我们在迭代过程中也使用一个栈来辅助,帮助我们能够返回上一层节点,就可以获得递归的效果。
我们先按照中-右-左的顺序获得遍历结果,最后将其翻转就是后序遍历结果啦。
- 遇到一个非空节点时,就将其入栈并遍历它的值(先中),并走到它的右子节点上去(再右);
- 当节点为空时,将栈顶弹出,并走到栈顶节点的左子节点上去(后左);
- 循环前两步,直到栈空结束,将最终得到的遍历结果反转。
(注意:入栈的节点一定已经被遍历过了)
时间复杂度O(n),空间复杂度O(n)
完整代码
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
ans = []
def postorder(root, ans):
# 当前遍历顺序为 中-右-左,最后将得到结果反转,即是后续遍历的顺序。
stack = []
while stack or root:
if root:
# 先遍历当前节点,再将当前节点转移到其右子树
stack.append(root)
ans.append(root.val)
root = root.right
else:
# 当右子树到头时,再返回上一节点,遍历左边
root = stack.pop().left
return ans.reverse()
postorder(root, ans)
return ans
看到这里,我们简单变通一下就可以得到前序遍历的迭代算法~ 代码如下
大家可以在Leetcode144. 二叉树的前序遍历上试一下。
前序:中-左-右,在迭代时先走到左子树,再弹出栈顶,并到其右子树即可。最后也不需要反转。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
ans = []
def postorder(root, ans):
# 当前遍历顺序为 中-右-左,即是前序遍历顺序。
stack = []
while stack or root:
if root:
# 先遍历当前节点,再将当前节点转移到其左子树
stack.append(root)
ans.append(root.val)
root = root.left
else:
# 当右子树到头时,再返回上一节点,遍历右边
root = stack.pop().right
postorder(root, ans)
return ans
那么对于中序遍历呢? 想法可能稍复杂一些,中序:左-中-右。这要求我们在遍历每个节点时,将其入栈但不要把其值取出来。要先遍历到节点的最左边,再逐一出栈取值,这样才能满足先左后中的顺序。取值后再遍历右子树。即:
入栈不取值,出栈取值
代码如下
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
stack = []
res = []
while stack or root:
if root:
stack.append(root)
root = root.left # 一路向左入栈
else:
root = stack.pop()
res.append(root.val) # 出栈时取节点值
root = root.right
return res
3. Morris后序遍历
解题思路
直接使用我们刚刚介绍的Morris后序遍历算法就可以啦~
时间复杂度O(n),空间复杂度O(1)
完整代码
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
self.ans = []
def add_path(cur):
tmp = []
while cur:
tmp.append(cur.val)
cur = cur.right
self.ans += tmp[::-1]
cur = root
while cur:
pre = cur.left
if pre:
while pre.right and pre.right!=cur:
pre = pre.right
if not pre.right:
pre.right = cur
cur = cur.left
continue
else:
pre.right = None
add_path(cur.left)
cur = cur.right
add_path(root)
return self.ans