Leetcode145. Morris後序遍歷

辣椒油li發表於2020-09-29


前言

前面我們遇到了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出發

  1. 如果當前節點的左子節點為空,則遍歷當前節點的右子節點;
  2. 如果當前節點的左子節點不為空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點;
  • 如果前驅節點的右子節點為空,將前驅節點的右子節點設定為當前節點,當前節點更新為當前節點的左子節點。
    - 如果前驅節點的右子節點為當前節點,將它的右子節點重新設為空。倒序輸出從當前節點的左子節點到該前驅節點這條路徑上的所有節點。當前節點更新為當前節點的右子節點。

和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

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

相關文章