1. 程式人生 > >資料結構(七)之樹

資料結構(七)之樹

二叉查詢樹查詢插入和刪除的時間複雜度都為O(log N)。但它有個弊端。如果輸入的資料是排序資料,那麼代價巨大,因為樹將只由那麼沒有左(或右)兒子的節點組成。一種解決方法是找平衡條件:任何節點的深度不能過深。最老的一種平衡查詢樹,即AVL樹。另外,較新的方法是放棄平衡條件,允許樹有任何的深度,但是在每次操作之後要使用一個調整規則進行調整,使得後面的操作效率更高,這是自調整類結構,例如伸展樹。

1. AVL樹

AVL樹是每個節點的左子樹和右子樹的高度最多差1的二叉查詢樹。除去可能的插入外,所有的樹查詢都可以以時間O(log N)執行。插入一個節點要靠旋轉來修正:旋轉有兩種情況,一是插入發生在“外邊的情況”,通過單旋轉來調整。二是發生在“內部”的情形,通過“雙旋轉”來實現。除旋轉引起的區域性變化外,程式設計人員必須記住:樹的其他部分必須知曉該變化。


對AVL樹的刪除多少要比插入複雜,如果刪除操作相對較少,那麼懶惰刪除恐怕是最好的策略。

2. 伸展樹

伸展樹的基本想法是,當一個節點被訪問後,它就要經過一系列AVL樹的旋轉被放到根上。另外,伸展樹還不要求保留高度或平衡資訊。
保證從空樹開始任意連續M次對樹的操作最多花費O(Mlog N)的時間。
展開思路:從底部向上沿著訪問路徑旋轉。如果X的父節點是樹根,那麼我們只要旋轉X和樹根。這是最後的旋轉。如果出現之字型,則像AVL那樣雙旋轉。如果出現一字型,則將左邊的樹變換成右邊樹。


我們可以通過訪問要刪除的節點實行刪除操作。將節點推到根處,然後找到左子樹中最大的元素然後旋轉到左子樹下,此時的左子樹沒有右兒子,我們將可以使右子樹稱為右兒子從而結束刪除。對於伸展樹分析很困難,但程式設計要比AVL樹簡單得多,這是因為要考慮得情形少許並且沒有平衡資訊需要儲存。

3. B樹

迄今為止,我們始終假設可以把整個資料結構儲存到計算機的主存中。可是,如果資料太多主存裝不下,那麼就意味著必須把資料結構放在磁碟上。此時,因為大O模型不再使用,所以導致規則發生了變化。
一棵M叉查詢樹可以有M路分支,隨著分支增加,樹的深度在減少。一顆完全M叉樹的高度約為logMN
B樹是平衡M-路樹,是常用的查詢樹。
階為M的B-樹定義:

> 資料項儲存在樹葉上
> 非葉節點儲存直到M-1個鍵,以指示搜尋的方向;鍵i代表子樹i+1中得知最小的鍵
> 樹的根或者是一片樹葉,或者其兒子數在2到M之間
> 除根外,所有非樹葉節點的兒子數在[M/2]到M之間
> 所有的樹葉都在相同的深度上並有[L/2]和L之間個數據項

L=5時,

4階B-樹稱為2-3-4樹,3階B-樹叫做2-3樹。


B-樹的深度最多是[logM/2N],我們執行O(log M)時間的工作量以確定選擇哪個分支(使用折半查詢),但是Insert和Delete運算可能需要O(M)的工作量來調整該節點該節點上的所有資訊,因此,對於每個Insert和Delete,最壞情形的執行時間為O(MlogMN)=O((M/logM)logN)。不過,對於一次Find只花費O(log N)的時間。經驗指出,從執行時間考慮,M的最好選擇是M=3或4。
B-樹實際用於資料庫系統,在那裡樹被儲存在物理的磁碟上而不是主存中。一般來說,對磁碟的訪問要比任何的主存操作慢幾個數量級。如果我們使用M-階B-樹,那麼磁碟訪問次數是O(logMN)。雖然每次磁碟訪問花費O(logM)來確定分支的方向,但是執行該操作的時間一般比讀儲存器的區塊所花費的時間少得多。

總結

我們已經看到樹在作業系統、編譯器設計以及查詢中的應用。表示式樹是更一般結構即所謂的分析樹的一個小例子,分析樹是編譯器設計中的核心資料結構。查詢樹在演算法設計中是非常重要的。它們幾乎支援所有有用的操作。查詢樹的非遞迴實現多少要快一些,但是遞迴實現更講究、更精彩,而且易於理解和除錯。查詢樹的問題在於,其效能嚴重的依賴於輸入,而輸入則是隨機的。不改變樹的操作都可以使用標準二叉查詢樹的程式。改變樹的操作必須將樹恢復。在伸展樹中的節點可以達到任意深度,但是在每次訪問之後樹又以多少有些神祕的方式被調整。任意連續M次操作花費O(M log N)的時間,它與平衡樹花費的時間相同。B-樹是平衡M-路樹,它能很好的匹配磁碟。其特殊情形是2-3樹,它是實現平衡查詢樹的另一種常用方法。在實踐中,所有平衡樹方案的執行時間都不如簡單二叉查詢樹省時。