1. 程式人生 > >淺談演算法和資料結構: 八 平衡查詢樹之2-3樹

淺談演算法和資料結構: 八 平衡查詢樹之2-3樹

前面介紹了二叉查詢樹(Binary Search Tree),他對於大多數情況下的查詢和插入在效率上來說是沒有問題的,但是他在最差的情況下效率比較低。本文及後面文章介紹的平衡查詢樹的資料結構能夠保證在最差的情況下也能達到lgN的效率,要實現這一目標我們需要保證樹在插入完成之後始終保持平衡狀態,這就是平衡查詢樹(Balanced Search Tree)。在一棵具有N 個節點的樹中,我們希望該樹的高度能夠維持在lgN左右,這樣我們就能保證只需要lgN次比較操作就可以查詢到想要的值。不幸的是,每次插入元素之後維持樹的平衡狀態太昂貴。所以這裡會介紹一些新的資料結構來保證在最壞的情況下插入和查詢效率都能保證在對數的時間複雜度內完成。本文首先介紹2-3查詢樹(2-3 Search Tree),後面會在此基礎上介紹紅黑樹和B樹。

定義

和二叉樹不一樣,2-3樹執行每個節點儲存1個或者兩個的值。對於普通的2節點(2-node),他儲存1個key和左右兩個自己點。對應3節點(3-node),儲存兩個Key,2-3查詢樹的定義如下:

1. 要麼為空,要麼:

2. 對於2節點,該節點儲存一個key及對應value,以及兩個指向左右節點的節點,左節點也是一個2-3節點,所有的值都比key有效,有節點也是一個2-3節點,所有的值比key要大。

3. 對於3節點,該節點儲存兩個key及對應value,以及三個指向左中右的節點。左節點也是一個2-3節點,所有的值均比兩個key中的最小的key還要小;中間節點也是一個2-3節點,中間節點的key值在兩個跟節點key值之間;右節點也是一個2-3節點,節點的所有key值比兩個key中的最大的key還要大。

如果中序遍歷2-3查詢樹,就可以得到排好序的序列。在一個完全平衡的2-3查詢樹中,根節點到每一個為空節點的距離都相同。

Definition of 2-3 tree

查詢

在進行2-3樹的平衡之前,我們先假設已經處於平衡狀態,我們先看基本的查詢操作。

2-3樹的查詢和二叉查詢樹類似,要確定一個樹是否屬於2-3樹,我們首先和其跟節點進行比較,如果相等,則查詢成功;否則根據比較的條件,在其左中右子樹中遞迴查詢,如果找到的節點為空,則未找到,否則返回。查詢過程如下圖:

search in 2-3 tree

插入

往一個2-node節點插入

往2-3樹中插入元素和往二叉查詢樹中插入元素一樣,首先要進行查詢,然後將節點掛到未找到的節點上。2-3樹之所以能夠保證在最差的情況下的效率的原因在於其插入之後仍然能夠保持平衡狀態。如果查詢後未找到的節點是一個2-node節點,那麼很容易,我們只需要將新的元素放到這個2-node節點裡面使其變成一個3-node節點即可。但是如果查詢的節點結束於一個3-node節點,那麼可能有點麻煩。

insert new node into 2-node

往一個3-node節點插入

往一個3-node節點插入一個新的節點可能會遇到很多種不同的情況,下面首先從一個最簡單的只包含一個3-node節點的樹開始討論。

只包含一個3-node節點

Insert into a single 3-node

如上圖,假設2-3樹只包含一個3-node節點,這個節點有兩個key,沒有空間來插入第三個key了,最自然的方式是我們假設這個節點能存放三個元素,暫時使其變成一個4-node節點,同時他包含四個子節點。然後,我們將這個4-node節點的中間元素提升,左邊的節點作為其左節點,右邊的元素作為其右節點。插入完成,變為平衡2-3查詢樹,樹的高度從0變為1。

節點是3-node,父節點是2-node

和第一種情況一樣,我們也可以將新的元素插入到3-node節點中,使其成為一個臨時的4-node節點,然後,將該節點中的中間元素提升到父節點即2-node節點中,使其父節點成為一個3-node節點,然後將左右節點分別掛在這個3-node節點的恰當位置。操作如下圖:

Insert into a 3-node whose parent is a 2-node

節點是3-node,父節點也是3-node

當我們插入的節點是3-node的時候,我們將該節點拆分,中間元素提升至父節點,但是此時父節點是一個3-node節點,插入之後,父節點變成了4-node節點,然後繼續將中間元素提升至其父節點,直至遇到一個父節點是2-node節點,然後將其變為3-node,不需要繼續進行拆分。

Insert into a 3-node whose parent is a 3-node

根節點分裂

當根節點到位元組點都是3-node節點的時候,這是如果我們要在位元組點插入新的元素的時候,會一直查分到跟節點,在最後一步的時候,跟節點變成了一個4-node節點,這個時候,就需要將跟節點查分為兩個2-node節點,樹的高度加1,這個操作過程如下:

Insert into a 3-node whose parent is a 3-node

本地轉換

將一個4-node拆分為2-3node涉及到6種可能的操作。這4-node可能在跟節點,也可能是2-node的左子節點或者右子節點。或者是一個3-node的左,中,右子節點。所有的這些改變都是本地的,不需要檢查或者修改其他部分的節點。所以只需要常數次操作即可完成2-3樹的平衡。

splite a 4-node is a local transformation

性質

這些本地操作保持了2-3樹的平衡。對於4-node節點變形為2-3節點,變形前後樹的高度沒有發生變化。只有當跟節點是4-node節點,變形後樹的高度才加一。如下圖所示:

global property of 2-3 tree

分析

完全平衡的2-3查詢樹如下圖,每個根節點到葉子節點的距離是相同的:

perfect balanced 2-3 tree

2-3樹的查詢效率與樹的高度是息息相關的。

  • 在最壞的情況下,也就是所有的節點都是2-node節點,查詢效率為lgN
  • 在最好的情況下,所有的節點都是3-node節點,查詢效率為log3N約等於0.631lgN

距離來說,對於1百萬個節點的2-3樹,樹的高度為12-20之間,對於10億個節點的2-3樹,樹的高度為18-30之間。

對於插入來說,只需要常數次操作即可完成,因為他只需要修改與該節點關聯的節點即可,不需要檢查其他節點,所以效率和查詢類似。下面是2-3查詢樹的效率:

analysis of 2-3 tree

實現

直接實現2-3樹比較複雜,因為:

  1. 需要處理不同的節點型別,非常繁瑣
  2. 需要多次比較操作來將節點下移
  3. 需要上移來拆分4-node節點
  4. 拆分4-node節點的情況有很多種

2-3查詢樹實現起來比較複雜,在某些情況插入後的平衡操作可能會使得效率降低。在2-3查詢樹基礎上改進的紅黑樹不僅具有較高的效率,並且實現起來較2-3查詢樹簡單。

但是2-3查詢樹作為一種比較重要的概念和思路對於後文要講到的紅黑樹和B樹非常重要。希望本文對您瞭解2-3查詢樹有所幫助。