Morris中序遍歷

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

Morris中序遍歷

一般二叉樹的中序遍歷需要用到遞迴演算法,在遞迴過程中,程式會自動使用遞迴棧,保證了我們可以在遍歷完左子樹後,返回到當前節點繼續後面的遍歷。但是遞迴棧也佔用了部分空間,因此出現Morris中序遍歷方法,使得我們中序遍歷的空間複雜度變為O(1),即與樹的深度無關。

我們結合Leetcode中第501題二叉搜尋樹中的眾數,來學習Morris中序遍歷演算法

以下使用python3~

Morris中序遍歷演算法詳解

本部分參考leetcode501官方題解
在中序遍歷的時候,一定先遍歷左子樹,然後遍歷當前節點,最後遍歷右子樹。在常規方法中,我們用遞迴回溯或者是棧來保證遍歷完左子樹可以再回到當前節點,但這需要我們付出額外的空間代價。
我們需要用一種巧妙地方法可以在 O(1) 的空間下,遍歷完左子樹可以再回到當前節點。我們希望當前的節點在遍歷完當前點的前驅之後被遍歷,我們可以考慮修改它的前驅節點的 right 指標。

 這樣做的原因在於,我們希望通過前驅節點能夠找到當前應該遍歷的節點。通過使前驅節點的 right 指標指向當前節點,就可以實現這個想法。

在Morris中序遍歷的程式執行中,實際抽象過程如下:

  1. 初始化一個cur指標,從根節點一直走到其左子樹為空的節點。在這“一路向左”的過程中,不對節點值進行遍歷,只是經過。同時要做一件重要的事情:使cur節點的前驅節點的 right 指標指向它。
  2. 當cur指標走到了樹的最左葉子節點後,遍歷該節點值。之後,它要沿著上一步設定好的right指標,往回走(一路向右),這次在走的過程中,需要對路過的節點值進行遍歷。

為了實現上面的抽象過程,程式中的具體演算法如下:

  1. 如果當前節點沒有左子樹,則遍歷這個點,然後跳轉到當前節點的右子樹。

  2. 如果當前節點有左子樹,那麼它的前驅節點一定在左子樹上,我們可以在左子樹上一直向右行走,找到當前點的前驅節點。

    如果前驅節點沒有右子樹,就將前驅節點的 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題目描述

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

相關文章