數據結構之二叉樹——二叉查找樹

定義

二叉查找樹(Binary Search Tree),又稱爲二叉搜索樹,二叉排序樹。它能夠是一棵空樹,若是不是空樹,則具備下列的性質:python

  • 非空左子樹的全部鍵值小於其根結點的鍵值
  • 非空右子樹的全部鍵值大於其根結點的鍵值
  • 左、右子樹都是二叉查找樹

好比下面兩棵樹,左邊的樹,由於5小於10,應該在10的左子樹上,所以不是二叉查找樹,右邊的樹則符合二叉查找樹的條件。 算法

因爲二叉查找樹的特性, 中序遍歷二叉查找樹,獲得的就是一個升序的數列。以上圖右邊的樹爲例,它的中序遍歷則是:15 30 33 41 50

查找

二叉查找樹的查找過程,能夠分爲下面的步驟:優化

  • 從根結點開始,若是樹爲空,則返回NULL
  • 若是樹不爲空,則根結點的值與查找值X進行比較,並進行不一樣的處理
    • 若是X等於根結點的值,則查找完成,返回結點
    • 若是X小於根結點的值,則在左子樹中繼續查找
    • 若是X大於根結點的值,則在右子樹中繼續查找

下圖以查找33爲例,展現了查找的軌跡spa

根據上面的步驟,可使用遞歸寫出該算法

def find(root: TreeNode, key):
    if not root:
        return None
    if key == root.val:
        return root
    elif key < root.val:
        return find(root.left, key)
    else:
        return find(root.right, key)
複製代碼

固然,上面的遞歸算法中存在尾遞歸,通常的編譯器都會自動優化尾遞歸,咱們也能夠將代碼改成迭代的方式3d

def find(root: TreeNode, key):
    if not root:
        return None
    while root:
        if key == root.val:
            return root
        elif key < root.val:
            root = root.left
        else:
            root = root.right
複製代碼

能夠看的出來,算法的複雜度和樹的高度有關,每一次對比以後都會捨棄掉原來數據的一半,所以算法的時間複雜爲O(logn)code

最小元素與最小元素

根據二叉查找樹的性質:cdn

  • 最小元素必定是在樹的最左分枝的端結點上
  • 最大元素必定是在樹的最右分枝的端結點上
    算法的實現也比較清晰
def findMax(root: TreeNode):
    if root:
        while root.right:
            root = root.right
    return root


def findMin(root: TreeNode):
    if root:
        while root.left:
            root = root.left
    return root
複製代碼

插入結點

因爲二叉查找樹具備特定的性質,所以在插入新的結點的時候,也要保證二叉查找樹的性質,整個插入新結點的過程與查找的過程相似,blog

以插入35爲例,下圖展現了插入35的軌跡 排序

以插入32爲例,下圖展現例插入32的軌跡

def insert(root: TreeNode, key):
    if not root:
        return TreeNode(key) # 若是是空樹,則新建根結點
    while root:
        if key == root.val:
            break # 若是插入的key已經存在,則直接退出循環
        elif key < root.val:
            # 小於向左子樹查找
            if not root.left:
                root.left = TreeNode(key)
            else:
                root = root.left
        else:
            # 大於向右子樹查找
            if not root.right:
                root.right = TreeNode(key)
            else:
                root = root.right
    return root
複製代碼

刪除結點

刪除操做比較複雜,要分狀況談論:遞歸

要刪除的結點爲葉子結點

這種狀況下,直接刪除。以下圖要刪除35結點

要刪除的結點只有一個孩子結點

這種狀況下,直接將要刪除結點的孩子結點,向上提,替代要刪除的結點,以下圖要刪除33結點

要刪除的結點有左、右兩棵子樹

這種狀況下,能夠有兩種方案:

  • 左子樹的最大元素替代要刪除結點
  • 右子樹的最小元素替代要刪除結點

若是要刪除41結點(使用左子樹最大元素代替)

左子樹的最大元素,依舊比右子樹的全部元素小,可是比其餘左子樹的元素大,所以它成爲新的根結點的時候,依舊能保持左子樹下的全部元素比根結點小,右子樹下的全部元素比根結點大。

若是要刪除41結點(使用右子樹最小元素代替)

右子樹的最小元素,依舊比左子樹的全部元素大,可是比其餘右子樹的元素小,成爲新的根結點,依舊能保持二叉查找樹的性質

綜合上面的狀況,代碼以下

def delete(root: TreeNode, key):
    if not root:
        return None
    if key < root.val:
        root.left = delete(root.left, key)
    elif key > root.val:
        root.right = delete(root.right, key)
    else:
        if root.left and root.right:
            # 有左右孩子
            leftMax = findMax(root.left)
            root.val = leftMax.val
            root.left = delete(root.left, leftMax.val)
        elif not root.left and not root.right:
            # 葉子結點
            root = None
        elif root.left:
            # 只有左孩子
            root = root.left
        elif root.right:
            # 只有右孩子
            root = root.right
    return root
複製代碼

在有左右孩子的狀況下,實際上只是把替代結點的值賦值給要刪除結點的值真正刪除的是原來的替代結點,好比要刪除41結點,替代結點爲35,則把41改成35,而後刪除原來的35結點。

總結

二叉查找樹將數據按照順序組織好,排列成二叉樹結構,所以相關算法的複雜度基本取決於樹的高度,若是在構建二叉查找樹的時候,不注意樹的高度,容易構建成一棵斜樹,這樣二叉查找樹就失去了優點。

二叉查找樹有較高的插入和刪除效率,而且具有較高的查找效率,對組織動態數據比較友好。

Thanks!

相關文章
相關標籤/搜索