1. 程式人生 > 實用技巧 >劍指offer_36_二叉搜尋樹與雙向連結串列

劍指offer_36_二叉搜尋樹與雙向連結串列

二叉搜尋樹與雙向連結串列

題目連結https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/

題目描述:輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的迴圈雙向連結串列。要求不能建立任何新的節點,只能調整樹中節點指標的指向。

為了讓您更好地理解問題,以下面的二叉搜尋樹為例:

我們希望將這個二叉搜尋樹轉化為雙向迴圈連結串列。連結串列中的每個節點都有一個前驅和後繼指標。對於雙向迴圈連結串列,第一個節點的前驅是最後一個節點,最後一個節點的後繼是第一個節點。

下圖展示了上面的二叉搜尋樹轉化成的連結串列。“head” 表示指向連結串列中有最小元素的節點。

特別地,我們希望可以就地完成轉換操作。當轉化完成以後,樹中節點的左指標需要指向前驅,樹中節點的右指標需要指向後繼。還需要返回連結串列中的第一個節點的指標。

題目解析

題目解析內容來自於題解中Krahets

題目的意思還是很明確的。基於二叉搜尋樹的性質,我們使用中序遍歷可得到二叉搜尋樹的遞增序列

將 二叉搜尋樹 轉換成一個 排序的迴圈雙線連結串列 ,其中包含三個要素:

  1. 排序連結串列:節點應該從小到大排序,因此應使用 中序遍歷 從小到大 訪問數節點;
  2. 雙向連結串列:在構建相鄰節點(設前驅節點 pre,當前節點 cur )關係時,不僅應 pre.right = cur, cur.left = pre。
  3. 迴圈連結串列:設連結串列頭結點 head 和尾結點 tail , 則應構建 head.left = tail 和 tail.right = head。

中序遍歷 為 “左 根 右” 順序,遞迴實現程式碼如下:

# 中序遍歷
def in_traversal(root):
    ret = []
    def traversal(root):
        if not root:
            return
        traversal(root.left)
        ret.append(root.val)
        traversal(root.right)
    traversal(root)
    return ret

根據以上分析,考慮使用中序遍歷訪問樹的各節點 cur ;並在訪問每個節點時構建 cur 和前驅節點 pre 的引用指向;中序遍歷完成後,最後構建頭節點和尾節點的引用指向即可。

演算法流程

in_traversal(cur): 遞迴法中序遍歷

  1. 終止條件:當節點 cur 為空時,代表已經越過葉子節點,此時可直接返回

  2. 遞迴左子樹,即 in_traversal(cur.left)

  3. 構建連結串列

    1. pre 為空時:代表當前訪問的是連結串列的頭結點,記為 head
      1. pre 不為空時:修改雙向節點引用,即 pre.right = cur, cur.left = pre;
      2. 儲存 cur:更新 pre = cur, 即節點 cur 是後繼節點的 pre
  4. 遞迴右子樹,即 in_traversal(cur.right)

treeToDoublyList(root):

  1. 特例處理:若節點 root 為空,則直接返回;
  2. 初始化:空節點 pre
  3. 轉化為雙向連結串列:呼叫 in_traversal(root);
  4. 構建迴圈連結串列:中序遍歷完成後,head 指向頭節點,pre指向尾結點,因此修改 headpre 的雙向節點引用即可
  5. 返回值:返回連結串列的頭節點 head 即可

複雜度分析

  • 時間複雜度:O(N)
  • 空間複雜度:O(N)

程式碼

class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        def in_traversal(cur):
            if not cur:
                return 
            in_traversal(cur.left)  # 遞迴左子樹
            if self.pre:  # 修改節點引用
                self.pre.right, cur.left = cur, self.pre
            else:  # 記錄頭節點
                self.head = cur
            self.pre = cur  # 儲存 cur
            in_traversal(cur.right)  # 遞迴右子樹
            
        if not root:
            return
        self.pre = None
        in_traversal(root)
        self.head.left, self.pre.right = self.pre, self.head
        return self.head