一般二叉樹的中序遍歷需要用到遞迴演算法,在遞迴過程中,程式會自動使用遞迴棧,保證了我們可以在遍歷完左子樹後,返回到當前節點繼續後面的遍歷。但是遞迴棧也占用了部分空間,因此出現morris中序遍歷方法,使得我們中序遍歷的空間複雜度變為o(1),即與樹的深度無關。
我們結合leetcode中第501題二叉搜尋樹中的眾數,來學習morris中序遍歷演算法。
以下使用python3~
本部分參考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:
ifnot cur.left:
# cur走到最左..
....
.# 遍歷cur.val
cur = cur.right
continue
pre = cur.left
while pre.right and pre.right != cur:
pre = pre.right
ifnot pre.right:
pre.right = cur
cur = cur.left
else:.
....
..# 遍歷cur.val
這道題可以用最簡單的思路求解,比如,使用普通的中序遍歷演算法獲得乙個樹的雜湊表或字典,記錄每個節點值出現的次數,取出現次數最多的那乙個(或幾個)值作為結果返回。但是這樣做空間複雜度較高,我們現在希望用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# 相當於全域性變數
defupdate
(val)
:# 更新眾數列表
ifnot self.ans:
# 用遍歷獲得的第乙個數字初始化所有相關變數
self.count, self.base, self.max =
1, val,
1return
if val==self.base: self.count +=
1else
: self.count =
1 self.base = val
elif self.count>self.max:
self.ans =
[val]
self.max = self.count
defmorris_inorder
(root)
:# morris中序遍歷
cur, pre = root,
none
while cur:
ifnot cur.left:
update(cur.val)
# 使用當前節點值更新眾數列表
cur = cur.right
continue
pre = cur.left
while pre.right and pre.right != cur:
pre = pre.right
ifnot pre.right:
pre.right = cur
cur = cur.left
else
: update(cur.val)
# 使用當前節點值更新眾數列表
cur = cur.right
morris_inorder(root)
return self.ans
leetcode 兩數相加(騰訊50 1
思路 leetcode中已經設定了鍊錶,只需遍歷兩個待相加的列表,逐次相加即可。思想 分為三種情況,兩個相加的數長度相等,逐個遍歷進行相加。需處理滿10進1,設定乙個flag列表處理對應位置相加後 10的結果。便於遍歷下乙個位置時進行相加。即處理進1的操作 兩個數不等。提前乙個結束 在上述現象中,即...
leetcode騰訊精選練習50(1) 兩數相加
這是一道來自leetcode的演算法題目。給出兩個非空的鍊錶用來表示兩個非負的整數。其中,它們各自的位數是按照逆序的方式儲存的,並且它們的每個節點只能儲存 一位 數字。如果,我們將這兩個數相加起來,則會返回乙個新的鍊錶來表示它們的和。您可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。示例 輸...
leetcode 501 二叉搜尋樹中的眾數
給定乙個有相同值的二叉搜尋樹 bst 找出 bst 中的所有眾數 出現頻率最高的元素 假定 bst 有如下定義 結點左子樹中所含結點的值小於等於當前結點的值 結點右子樹中所含結點的值大於等於當前結點的值 左子樹和右子樹都是二叉搜尋樹 例如 給定 bst 1,null,2,2 返回 2 高階 你可以不...