Leetcode145. Morris後序遍歷
文章概覽
前言
前面我們遇到了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
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
相關文章
- ast 後序遍歷AST
- 【資料結構與演算法】二叉樹的 Morris 遍歷(前序、中序、後序)資料結構演算法二叉樹
- 二叉樹建立,前序遍歷,中序遍歷,後序遍歷 思路二叉樹
- 二叉樹的建立、前序遍歷、中序遍歷、後序遍歷二叉樹
- 二叉樹的 Morris 中序遍歷——O(1)空間複雜度二叉樹複雜度
- 面試中很值得聊的二叉樹遍歷方法——Morris遍歷面試二叉樹
- 二叉樹--後序遍歷二叉樹
- 資料結構與演算法——二叉樹的前序遍歷,中序遍歷,後序遍歷資料結構演算法二叉樹
- 刷題筆記:樹的前序、中序、後序遍歷筆記
- 二叉樹中序和後序遍歷表示式二叉樹
- 非遞迴實現先序遍歷和中序遍歷遞迴
- 二叉樹的前序、中序、後序三種遍歷二叉樹
- 二叉樹的先,中,後序遍歷二叉樹
- 二叉樹的先中後序遍歷二叉樹
- 二叉樹的前中後序遍歷二叉樹
- 二叉樹的四種遍歷方法:先序,中序,後序,層序二叉樹
- js物件遍歷順序JS物件
- 二叉樹的前序,中序,後序遍歷方法總結二叉樹
- 從中序與後序遍歷序列構造二叉樹二叉樹
- 資料結構 排序二叉樹(BST) 插入刪除查詢 中序遍歷 銷燬(後序遍歷)資料結構排序二叉樹
- 演算法 -- 實現二叉樹先序,中序和後序遍歷演算法二叉樹
- 144.二叉樹的前序遍歷145.二叉樹的後序遍歷 94.二叉樹的中序遍歷二叉樹
- 建立二叉樹:層次遍歷--樹的寬度高度,後序遍歷--祖先節點二叉樹
- 遞迴和迭代實現二叉樹先序、中序、後序和層序遍歷遞迴二叉樹
- jQuery 遍歷 – 後代jQuery
- 非遞迴遍歷二叉樹的四種策略-先序、中序、後序和層序遞迴二叉樹
- 二叉樹的後序遍歷post order演算法二叉樹演算法
- python-二叉樹:前、中、後、層序遍歷Python二叉樹
- 【模板題】- 145. 二叉樹的後序遍歷二叉樹
- JavaScript遍歷物件屬性順序JavaScript物件
- 層序遍歷二叉樹二叉樹
- 根據前序遍歷序列、中序遍歷序列,重建二叉樹二叉樹
- 二叉樹迭代器(中序遞迴、前序和後序遍歷)演算法二叉樹遞迴演算法
- Leetcode 590. N叉樹的後序遍歷(DAY 2)LeetCode
- 二叉樹的層序遍歷二叉樹
- 順序棧————遍歷、出棧、入棧
- 力扣#94 樹的中序遍歷力扣
- PAT 1043 Is It a Binary Search Tree (25分) 由前序遍歷得到二叉搜尋樹的後序遍歷