第12 章 二叉搜索樹

  搜索樹結構支持許多動態集合操作,因此,使用一棵搜索樹既可以作爲一個字典又可以作爲一個優先隊列。
  二叉搜索樹的基本操作所花費的時間與這棵樹的高度成正比。對於 n 個節點的一個完全二叉樹來說,這些操作的最壞運行時間爲θ(lgn)。然而,如果這棵樹是一條 n 個節點組成的線性鏈,那麼同樣的操作就要花費θ(n)的最壞運行時間。
  

12.1 什麼是二叉搜索樹

  一顆二叉搜索樹是以一顆二叉樹來組織的,這樣一棵樹可以用一個鏈表數據結構來表示,其中每個節點就是一個對象。除了 key 和衛星數據之外,每個節點還包含屬性left 、right、和p。如果某個孩子結點和父結點不存在,則相應屬性的值爲NIL。跟節點是樹中唯一父指針爲NIL的結點。
  二叉搜索樹:對任何節點 x,其左子樹(並不是左孩子)中的關鍵字最大不超過x.key,其右子樹中的關鍵字最小不低於x.key。不同的二叉樹可以代表同一組值的集合。
  二叉搜索樹按序輸出樹中的所有關鍵字的遞歸算法:中序遍歷算法。這樣命名的原因是輸出的子樹根的關鍵字位於其左子樹的關鍵字值和右子樹的關鍵字值之間。(類似地,先序遍歷中輸出的根的關鍵字在其左右子樹的關鍵字值之前,而後序遍歷輸出的根的關鍵字在其左右子樹的關鍵字值之後)。
INORDER-TREE-WALK(T.root)

if x != NIL
    INORDER-TREE-WALK(x.left)
    print x.key
    INORDER-TREE-WALK(x.right)

定理 12.1 如果 x 是一顆有 n 個結點子樹的根,那麼調用INORDER-TREE-WALK(x)需要θ(n)時間

12.2 查詢二叉搜索數

查找
  在一顆二叉搜索樹中查找一個具有給定關鍵字的結點。輸入一個指向樹根的指針和一個關鍵字 k。存在,返回一個指向關鍵字爲 k 的結點的指針;否則返回NIL。
TREE-SEARCH(x, k)

if x == NIL or k == x.key
    return x
if k < x.key
    return TREE-SEARCH(x.left, k)
else return TREE-SEARCH(x.right, k)

TREE-SEACH的運行時間爲O(h),其中 h 爲這顆數的高度。
同樣可以採用while循環來展開遞歸,用一種迭代方式重寫這個過程。對於大多數計算機,迭代版本的效率要高的多。
ITERATIVE-TREE-SEARCH(x, k)

while x!= NIL and k != x.key
    if k < x.key
        x = x.left
    else x = x.right
return x

最大關鍵字和最小關鍵字元素
TREE-MINIMUM(x)

while x.left != NIL
    x = x.left
return x

TREE-MAXIMUM(x)

while x.right != NIL
    x = x.right
return x

這兩個過程在一顆高度爲 h 的樹上均能在O(h)時間內完成。
後繼和前驅
TREE-SUCCESSOR(x)(後繼)

if x.right != NIL
    return TREE-MINIMUM(x.right)
y = x.p
while y!= NIL and x == y.right
    x = y
    y = y.p
return y

TREE-SUCCESSOR的運行時間爲O(h)。
定理12.2 在一顆高度爲 h 的二叉樹搜索樹上,動態集合上的操作SEARCH、MINIMUN、MAXIMUN、SUCCESSOR和PREDECESSOR可以在O(h)時間內完成

12. 3 插入和刪除

插入一個新結點帶來的樹修改要相對簡單些,而刪除的處理有些複雜。
插入
將一個新值 v插入到一個二叉搜索數T中,調用TREE-INSERT,該過程以結點z作爲輸入,其中z.key = v,z.left = NIL,z.right = NIL。
TREE-INSERT(T, z)

y = NIL
x = T.root
while x != NIL
    y = x
    if z.key < x.key
     x = x.left
    else x = x.right
z.p = y
if y == NIL
    T.root = z
else if z.key < y.key
    y.left = z
else y.right = z

TREE-INSERT從樹根開始,指針 x 記錄一條向下的簡單路徑,並查找替換的輸入項 z 的NIL。該過程保持遍歷指針 y 作爲 x 的雙親。初始化後,第 3~7 行的while循環使得這兩個指針沿樹向下移動,向左或向右取決於z.key和x.key的比較,知道x變爲NIL。這個NIL佔據的位置就是輸入項 z要放置的地方。需要遍歷指針 y,因爲找到 NIL時要知道z屬於那個節點。第 8~13 行設置相應的指針,使得 z 插入其中。
與其他搜索數的原始操作一樣,過程TREE-INSERT在一顆高度爲h的樹上的運行時間爲O(h)。
刪除
  從一顆二叉搜索樹 T 中刪除一個結點 z 的整個策略分爲三種基本情況,但只有一種情況棘手:
  

  • 如果 z 沒有孩子結點,那麼只是簡單的將它刪除,並修改它的父節點,用NIL作爲孩子來替換 z。
  • 如果z只有一個孩子,那麼將這個孩子提升到樹 中 z 的位置上,並修改 z 的父結點,用 z 的孩子來替換 z。
  • 如果 z 有兩個孩子,那麼找 z 的後繼 y(一定在 z 的右子樹中),並讓 y 佔據樹中 z 的位置。 z的原來右子樹部分成爲 y 的新的右子樹,並且 z 的左子樹成爲 y 的新左子樹。

      從一顆二叉搜索樹 T 中刪除一個給定的結點 z,這個過程取指向 T 和 z的指針作爲輸入參數。它與前面概括出的三種情況有些不同。
      

  • 如果 z 沒有左孩子,那麼用其右孩子來替換 z,這個右孩子可以是 NIL,也可以不是。當 z 的右孩子是 NIL時,此時這種情況歸爲 z 沒有孩子結點的情形。當 z 的右孩子非 NIL時,這種情況就是 z 僅有一個孩子節點的情形,該孩子就是其右孩子。
  • 如果 z 僅有一個孩子且爲其左孩子,那麼用其左孩子來替換 z。
  • 否則, z既有一個左孩子又有一個右孩子,要查找 z 的後繼 y,這個後繼位與 z 的右子樹中並且沒有左孩子。需要將 y 移除原來的位置進行拼接,並替換樹中的z。
  • 如果 y 是 z 的右孩子,那麼用 y 替換 z ,並僅留下 y 的右孩子。
  • 否則,y 位於 z 的右子樹中當並不是 z 的右孩子。在這種情況下,先用 y 的右孩子替換 y,然後再用 y 替換 z。
    爲了在二叉搜索樹中,定義一個子過程TRANSPLANT,它是用另一顆子樹替換一顆子樹併成爲其雙親的孩子結點。但TRANSPLANT用一顆以 v 爲根的子樹替換一顆以 u 爲根的子樹時,結點 u 的雙親就變爲 v 的雙親,並且最後 v成爲 u 的雙親的相應孩子。
    TRANSPLANT(T, u, v)
if u.p == NIL
    T.root = v
else if u == u.p. left
    u.p.left = v
else u.p.right = v
if v != NIL
    v.p = u.p

下面是從二叉搜索樹中刪除結點 z 的刪除過程:
TREE-DELETE(T, z)

if z.left == NIL
    TRANSPLANT(T, z, z.right)
else if z.right == NIL
    TRANSPLANT(T, z, z.left)
else y = TREE-MINIMUM(z.right)
    if y.p != z
        TRANSPLANT(T, y, y.right)
        y.right = z.right
        y.right.p = y
    TRANSPLANT(T, z, y)
    y.left = z.left
    y.left .p = y

TREE-DELETE的花費時間爲O(h)。
定理12.3 在一顆高度爲h 的二叉搜索樹上,實現動態集合操作INSERT和DELETE的運行時間爲O(h)。

12.4 隨機構建二叉搜索樹

  二叉搜索樹的高度h>= lgn.我們定義 n 個關鍵字的一顆隨機構建二叉搜索樹爲按隨機次序插入這些關鍵字到一顆初始爲空樹中而生成的數。
定理 12.4 一顆有 n 個不同關鍵字的隨機構建二叉搜索樹的期望高度爲O(lgn)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章