Morris中序遍歷
Morris中序遍歷
一般二叉樹的中序遍歷需要用到遞迴演算法,在遞迴過程中,程式會自動使用遞迴棧,保證了我們可以在遍歷完左子樹後,返回到當前節點繼續後面的遍歷。但是遞迴棧也佔用了部分空間,因此出現Morris中序遍歷方法,使得我們中序遍歷的空間複雜度變為O(1),即與樹的深度無關。
我們結合Leetcode中第501題二叉搜尋樹中的眾數,來學習Morris中序遍歷演算法。
以下使用python3~
Morris中序遍歷演算法詳解
本部分參考leetcode501官方題解
在中序遍歷的時候,一定先遍歷左子樹,然後遍歷當前節點,最後遍歷右子樹。在常規方法中,我們用遞迴回溯或者是棧來保證遍歷完左子樹可以再回到當前節點,但這需要我們付出額外的空間代價。
我們需要用一種巧妙地方法可以在 O(1) 的空間下,遍歷完左子樹可以再回到當前節點。我們希望當前的節點在遍歷完當前點的前驅之後被遍歷,我們可以考慮修改它的前驅節點的 right 指標。
這樣做的原因在於,我們希望通過前驅節點能夠找到當前應該遍歷的節點。通過使前驅節點的 right 指標指向當前節點,就可以實現這個想法。
在Morris中序遍歷的程式執行中,實際抽象過程如下:
- 初始化一個cur指標,從根節點一直走到其左子樹為空的節點。在這“一路向左”的過程中,不對節點值進行遍歷,只是經過。同時要做一件重要的事情:使cur節點的前驅節點的 right 指標指向它。
- 當cur指標走到了樹的最左葉子節點後,遍歷該節點值。之後,它要沿著上一步設定好的right指標,往回走(一路向右),這次在走的過程中,需要對路過的節點值進行遍歷。
為了實現上面的抽象過程,程式中的具體演算法如下:
-
如果當前節點沒有左子樹,則遍歷這個點,然後跳轉到當前節點的右子樹。
-
如果當前節點有左子樹,那麼它的前驅節點一定在左子樹上,我們可以在左子樹上一直向右行走,找到當前點的前驅節點。
如果前驅節點沒有右子樹,就將前驅節點的 right 指標指向當前節點。這一步是為了在遍歷完前驅節點後能找到前驅節點的後繼,也就是當前節點。
如果前驅節點的右子樹為當前節點,說明前驅節點已經被遍歷過並被修改了 right 指標,這個時候遍歷當前的點,然後跳轉到當前節點的右子樹,繼續後面的遍歷。
程式碼實現:
def morris_inorder(root):
cur, pre = root, None # pre為前驅節點, cur為當前節點
while cur:
if not cur.left: # cur走到最左
.......# 遍歷cur.val
cur = cur.right
continue
pre = cur.left
while pre.right and pre.right != cur:
pre = pre.right
if not pre.right:
pre.right = cur
cur = cur.left
else:
.......# 遍歷cur.val
cur = cur.right
Leetcode 501題目描述
解題思路
這道題可以用最簡單的思路求解,比如,使用普通的中序遍歷演算法獲得一個樹的雜湊表或字典,記錄每個節點值出現的次數,取出現次數最多的那一個(或幾個)值作為結果返回。但是這樣做空間複雜度較高,我們現在希望用O(1)的空間複雜度解決這個問題。
那麼我們需要改變兩個方面:
1. 將普通的中序遍歷替換為Morris中序遍歷。
2. 將“使用雜湊表/字典找出現次數最多的值”替換為其他方法。
第一個方面我們已經知道如何做了,下面就是對第二點的改進。
其實尋找出現最多的數,在我們遍歷二叉搜尋樹的時候就可以順便做到。
在中序遍歷有重複數字的二叉搜尋樹時,我們獲得的數字一定是按照單調非減順序排列的。
我們可以設定幾個值來記錄上一次獲得的值base、當前獲得的值cur_val、當前值已經出現的次數count,和當前元素出現次數的最大值Max。還要維護一個存放當前已經找到的眾數的列表ans。
當cur_val=base時,count += 1;如不相等,更新base為cur_val,count=1(重新計數)
當count=Max時,說明當前值也是一個眾數,將cur_val放入ans中;
若count>Max,說明當前值出現次數比之前找到的眾數要多,則更新ans=[cur_val],Max=count。
上述的過程我們封裝到update函式中,在每遍歷到一個節點時,執行該函式即可。
完整程式碼
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def findMode(self, root: TreeNode) -> List[int]:
self.ans, self.count, self.Max, self.base = [], 0, 0, 0 # 相當於全域性變數
def update(val): # 更新眾數列表
if not self.ans: # 用遍歷獲得的第一個數字初始化所有相關變數
self.ans.append(val)
self.count, self.base, self.Max = 1, val, 1
return
if val==self.base: self.count += 1
else:
self.count = 1
self.base = val
if self.count==self.Max: self.ans.append(val)
elif self.count>self.Max:
self.ans = [val]
self.Max = self.count
def morris_inorder(root): # Morris中序遍歷
cur, pre = root, None
while cur:
if not cur.left:
update(cur.val) # 使用當前節點值更新眾數列表
cur = cur.right
continue
pre = cur.left
while pre.right and pre.right != cur:
pre = pre.right
if not pre.right:
pre.right = cur
cur = cur.left
else:
update(cur.val) # 使用當前節點值更新眾數列表
cur = cur.right
morris_inorder(root)
return self.ans
相關文章
- Leetcode145. Morris後序遍歷LeetCode
- 二叉樹的 Morris 中序遍歷——O(1)空間複雜度二叉樹複雜度
- 【資料結構與演算法】二叉樹的 Morris 遍歷(前序、中序、後序)資料結構演算法二叉樹
- 面試中很值得聊的二叉樹遍歷方法——Morris遍歷面試二叉樹
- 非遞迴實現先序遍歷和中序遍歷遞迴
- 7-1 根據後序和中序遍歷輸出先序遍歷 (25 分)
- ast 後序遍歷AST
- 力扣#94 樹的中序遍歷力扣
- 根據前序遍歷序列、中序遍歷序列,重建二叉樹二叉樹
- L2_006樹的遍歷(後序+中序->前序/層序)
- js物件遍歷順序JS物件
- 二叉樹中序和後序遍歷表示式二叉樹
- 刷題筆記:樹的前序、中序、後序遍歷筆記
- 二叉樹的前序、中序、後序三種遍歷二叉樹
- 二叉樹的四種遍歷方法:先序,中序,後序,層序二叉樹
- 二叉樹的先中後序遍歷二叉樹
- 二叉樹的先,中,後序遍歷二叉樹
- 二叉樹的前中後序遍歷二叉樹
- 144.二叉樹的前序遍歷145.二叉樹的後序遍歷 94.二叉樹的中序遍歷二叉樹
- 從中序與後序遍歷序列構造二叉樹二叉樹
- 二叉樹的前序,中序,後序遍歷方法總結二叉樹
- 演算法 -- 實現二叉樹先序,中序和後序遍歷演算法二叉樹
- LeeCode-94. 二叉樹的中序遍歷二叉樹
- 94. 二叉樹的中序遍歷(迭代)二叉樹
- Leetcode——94.二叉樹的中序遍歷LeetCode二叉樹
- 二叉樹--後序遍歷二叉樹
- JavaScript遍歷物件屬性順序JavaScript物件
- 根據二叉樹的前序遍歷和中序遍歷輸出二叉樹;二叉樹
- 遞迴和迭代實現二叉樹先序、中序、後序和層序遍歷遞迴二叉樹
- 【模板題】- 94. 二叉樹的中序遍歷二叉樹
- 二叉樹(BST)中序遍歷的三種方法二叉樹
- python-二叉樹:前、中、後、層序遍歷Python二叉樹
- 中序線索二叉樹的建立與遍歷二叉樹
- 144. 二叉樹的遍歷「前序、中序、後序」 Golang實現二叉樹Golang
- 順序棧————遍歷、出棧、入棧
- 二叉樹的層序遍歷二叉樹
- LeetCode-106-從中序與後序遍歷序列構造二叉樹LeetCode二叉樹
- 中序線索二叉樹的構造和遍歷二叉樹