紅黑樹(轉)

咱們使用符號表這個詞來描述一張抽象的表格,咱們會將信息(值)存儲在其中,而後按照指定的鍵來搜索並獲取這些信息。鍵和值的具體意義取決於不一樣的應用。面試

符號表中可能會保存不少鍵和不少信息,所以實現一張高效的符號表也是一項頗有挑戰性的任務。算法

咱們會用三種經典的數據類型來實現高效的符號表:二叉查找數、紅黑樹、散列表。數組

 

  1. 二分查找


咱們使用有序數組存儲鍵,經典的二分查找可以根據數組的索引大大減小每次查找所需的比較次數。數據結構

在查找時,咱們先將被查找的鍵和子數組的中間鍵比較。若是被查找的鍵小於中間鍵,咱們就在左子數組中繼續查找,若是大於咱們就在右子數組中繼續查找,不然中間鍵就是咱們要找的鍵。性能

 

通常狀況下二分查找都比順序查找快的多,它也是衆多實際應用程序的最佳選擇。對於一個靜態表(不容許插入)來講,將其在初始化時就排序是值得的。spa

 

固然,二分查找也不適合不少應用。現代應用須要同時可以支持高效的查找和插入兩種操做的符號表實現。也就是說,咱們須要在構造龐大的符號表的同時可以任意插入(也許還有刪除)鍵值對,同時也要可以完成查找操做。.net

 

要支持高效的插入操做,咱們彷佛須要一種鏈式結構。當單連接的鏈表是沒法使用二分查找的,由於二分查找的高效來自於可以快速經過索引取得任何子數組的中間元素。爲了將二分查找的效率和鏈表的靈活性結合起來,咱們須要更加複雜的數據結構。blog

可以同時擁有二者的就是二叉查找樹。排序

 

2.二叉查找樹遞歸


一顆二叉查找樹(BST)是一顆二叉樹,其中每一個節點都含有一個可比較的鍵(以及相關聯的值)且每一個結點的鍵都大於其左子樹中的任意結點的鍵而小於右子樹的任意結點的鍵。

 

一顆二叉查找樹表明了一組鍵(及其相應的值)的集合,而同一個集合能夠用多顆不一樣的二叉查找樹表示。

若是咱們將一顆二叉查找樹的全部鍵投影到一條直線上,保證一個結點的左子樹中的鍵出如今它的右邊,右子樹中的鍵出如今它的右邊,那麼咱們必定能夠獲得一條有序的鍵列。

 

 

 

  • 查找

 

在二叉查找樹中查找一個鍵的遞歸算法:

若是樹是空的,則查找未命中。若是被查找的鍵和根結點的鍵相等,查找命中。不然咱們就在適當的子樹中繼續查找。若是被查找的鍵較小就選擇左子樹,較大就選擇右子樹。

在二叉查找樹中,隨着咱們不斷向下查找,當前結點所表示的子樹的大小也在減少(理想狀況下是減半)

 

  • 插入

 

查找代碼幾乎和二分查找的同樣簡單,這種簡潔性是二叉查找樹的重要特性之一。而二叉查找樹的另外一個更重要的特性就是插入的實現難度和查找差很少。

當查找一個不存在於樹中的結點並結束於一條空連接時,咱們須要作的就是將連接指向一個含有被查找的鍵的新結點。若是被查找的鍵小於根結點的鍵,咱們會繼續在左子樹中插入該鍵,不然在右子樹中插入該鍵。

 

  • 分析

 

使用二叉查找樹的算法的運行時間取決於樹的形狀,而樹的形狀又取決於鍵被插入的前後順序。

在最好的狀況下,一顆含有N個結點的樹是徹底平衡的,每條空連接和根結點的距離都爲~lgN。在最壞的狀況下,搜索路徑上可能有N個結點。但在通常狀況下樹的形狀和最好狀況更接近。

 


咱們假設鍵的插入順序是隨機的。對這個模型的分析而言,二叉查找樹和快速排序幾乎就是「雙胞胎」。樹的根結點就是快速排序中的第一個切分元素(左側的鍵都比它小,右側的鍵都比它大),而這對於全部的子樹一樣適用,這和快速排序中對於子數組的遞歸排序徹底對應。

【在由N個隨機鍵構造的二叉查找樹中,查找命中平均所需的比較次數爲~2lgN。 N越大這個公式越準確】

 

3.平衡查找樹


在一顆含有N個結點的樹中,咱們但願樹高爲~lgN,這樣咱們就能保證全部查找都能在~lgN此比較內結束,就和二分查找同樣。不幸的是,在動態插入中保證樹的完美平衡的代價過高了。咱們放鬆對完美平衡的要求,使符號表API中全部操做均可以在對數時間內完成。

 

3.2-3查找樹


爲了保證查找樹的平衡性,咱們須要一些靈活性,所以在這裏咱們容許樹中的一個結點保存多個鍵。

2-結點:含有一個鍵(及值)和兩條連接,左連接指向的2-3樹中的鍵都小於該結點,右連接指向的2-3樹中的鍵都大於該結點。

3-結點:含有兩個鍵(及值)和三條連接,左連接指向的2-3樹中的鍵都小於該結點,中連接指向的2-3樹中的鍵都位於該結點的兩個鍵之間,右連接指向的2-3樹中的鍵都大於該結點。

(2-3指的是2叉-3叉的意思)

 

 

一顆完美平衡的2-3查找樹中的全部空連接到根結點的距離都是相同的。

 

  • 查找

 

要判斷一個鍵是否在樹中,咱們先將它和根結點中的鍵比較。若是它和其中的任何一個相等,查找命中。不然咱們就根據比較的結果找到指向相應區間的連接,並在其指向的子樹中遞歸地繼續查找。若是這是個空連接,查找未命中。

 

  • 插入

 

要在2-3樹中插入一個新結點,咱們能夠和二叉查找樹同樣先進行一次未命中的查找,而後把新結點掛在樹的底部。但這樣的話樹沒法保持完美平衡性。咱們使用2-3樹的主要緣由就在於它可以在插入以後繼續保持平衡。

若是未命中的查找結束於一個2-結點,咱們只要把這個2-結點替換爲一個3-結點,將要插入的鍵保存在其中便可。若是未命中的查找結束於一個3-結點,事情就要麻煩一些。

 

  • 熱身:

 

先考慮最簡單的例子:只有一個3-結點的樹,向其插入一個新鍵。

這棵樹惟一的結點中已經沒有可插入的空間了。咱們又不能把新鍵插在其空結點上(破壞了完美平衡)。爲了將新鍵插入,咱們先臨時將新鍵存入該結點中,使之成爲一個4-結點。建立一個4-結點很方便,由於很容易將它轉換爲一顆由3個2-結點組成的2-3樹(如圖所示),這棵樹既是一顆含有3個結點的二叉查找樹,同時也是一顆完美平衡的2-3樹,其中全部空連接到根結點的距離都相等。

 

 

向一個父結點爲2-結點的3-結點中插入新鍵

假設未命中的查找結束於一個3-結點,而它的父結點是一個2-結點。在這種狀況下咱們須要在維持樹的完美平衡的前提下爲新鍵騰出空間。

咱們先像剛纔同樣構造一個臨時的4-結點並將其分解,但此時咱們不會爲中鍵建立一個新結點,而是將其移動至原來的父結點中。(如圖所示)

 

此次轉換也並不影響(完美平衡的)2-3樹的主要性質。樹仍然是有序的,由於中鍵被移動到父結點中去了,樹仍然是完美平衡的,插入後全部的空連接到根結點的距離仍然相同。

 

向一個父結點爲3-結點的3-結點中插入新鍵

假設未命中的查找結束於一個3-結點,而它的父結點是一個3-結點。

咱們再次和剛纔同樣構造一個臨時的4-結點並分解它,而後將它的中鍵插入它的父結點中。但父結點也是一個3-結點,所以咱們再用這個中鍵構造一個新的臨時4-結點,而後在這個結點上進行相同的變換,即分解這個父結點並將它的中鍵插入到它的父結點中去。

咱們就這樣一直向上不斷分解臨時的4-結點並將中鍵插入更高的父結點,直至遇到一個2-結點並將它替換爲一個不須要繼續分解的3-結點,或者是到達3-結點的根。

 

  • 總結:

先找插入結點,若結點有空(即2-結點),則直接插入。如結點沒空(即3-結點),則插入使其臨時容納這個元素,而後分裂此結點,把中間元素移到其父結點中。對父結點亦如此處理。(中鍵一直往上移,直到找到空位,在此過程當中沒有空位就先搞個臨時的,再分裂。)

 

 

★2-3樹插入算法的根本在於這些變換都是局部的:除了相關的結點和連接以外沒必要修改或者檢查樹的其餘部分。每次變換中,變動的連接數量不會超過一個很小的常數。全部局部變換都不會影響整棵樹的有序性和平衡性。

 

{你肯定理解了2-3樹的插入過程了嗎? 若是你理解了,那麼你也就基本理解了紅黑樹的插入}

 

  • 構造

 

和標準的二叉查找樹由上向下生長不一樣,2-3樹的生長是由下向上的。

 


優勢

 

2-3樹在最壞狀況下仍有較好的性能。每一個操做中處理每一個結點的時間都不會超過一個很小的常數,且這兩個操做都只會訪問一條路徑上的結點,因此任何查找或者插入的成本都確定不會超過對數級別。

完美平衡的2-3樹要平展的多。例如,含有10億個結點的一顆2-3樹的高度僅在19到30之間。咱們最多隻須要訪問30個結點就能在10億個鍵中進行任意查找和插入操做。

 

  • 缺點

 

咱們須要維護兩種不一樣類型的結點,查找和插入操做的實現須要大量的代碼,並且它們所產生的額外開銷可能會使算法比標準的二叉查找樹更慢。

平衡一棵樹的初衷是爲了消除最壞狀況,但咱們但願這種保障所需的代碼可以越少越好。

 

 

4.紅黑二叉查找樹


【前言:本文所討論的紅黑樹之目的在於使讀者能更簡單清晰地瞭解紅黑樹的構造,使讀者能在紙上清晰快速地畫出紅黑樹,而不是爲了寫出紅黑樹的實現代碼。

如果要在代碼級理解紅黑樹,則勢必須要記住其複雜的插入和旋轉的各類狀況,我認爲那只有助於增長你們對紅黑樹的恐懼,實際面試和工做中幾乎不會遇到須要本身動手實現紅黑樹的狀況(不少語言的標準庫中就有紅黑樹的實現)。  若對於紅黑樹的C代碼實現有興趣的,可移步至July的博客。】

 

(理解紅黑樹一句話就夠了:紅黑樹就是用紅連接表示3-結點的2-3樹。那麼紅黑樹的插入、構造就可轉化爲2-3樹的問題,即:在腦中用2-3樹來操做,獲得結果,再把結果中的3-結點轉化爲紅連接便可。而2-3樹的插入,前面已有詳細圖文,實際也很簡單:有空則插,沒空硬插,再分裂。  這樣,咱們就不用記那麼複雜且讓人頭疼的紅黑樹插入旋轉的各類狀況了。只要清楚2-3樹的插入方式便可。  下面圖文詳細演示。)

 

  • 紅黑樹的本質:

紅黑樹是對2-3查找樹的改進,它能用一種統一的方式完成全部變換。

 

  • 替換3-結點

 

★紅黑樹背後的思想是用標準的二叉查找樹(徹底由2-結點構成)和一些額外的信息(替換3-結點)來表示2-3樹。

咱們將樹中的連接分爲兩種類型:紅連接將兩個2-結點鏈接起來構成一個3-結點,黑連接則是2-3樹中的普通連接。確切地說,咱們將3-結點表示爲由一條左斜的紅色連接相連的兩個2-結點。

這種表示法的一個優勢是,咱們無需修改就能夠直接使用標準二叉查找樹的get()方法。對於任意的2-3樹,只要對結點進行轉換,咱們均可以當即派生出一顆對應的二叉查找樹。咱們將用這種方式表示2-3樹的二叉查找樹稱爲紅黑樹。

 

  • 紅黑樹的另外一種定義是知足下列條件的二叉查找樹:

⑴紅連接均爲左連接。

⑵沒有任何一個結點同時和兩條紅連接相連。

⑶該樹是完美黑色平衡的,即任意空連接到根結點的路徑上的黑連接數量相同。

 

若是咱們將一顆紅黑樹中的紅連接畫平,那麼全部的空連接到根結點的距離都將是相同的。若是咱們將由紅連接相連的結點合併,獲得的就是一顆2-3樹。

相反,若是將一顆2-3樹中的3-結點畫做由紅色左連接相連的兩個2-結點,那麼不會存在可以和兩條紅連接相連的結點,且樹必然是完美平衡的。


 

不管咱們用何種方式去定義它們,紅黑樹都既是二叉查找樹,也是2-3樹。

(2-3樹的深度很小,平衡性好,效率高,可是其有兩種不一樣的結點,實際代碼實現比較複雜。而紅黑樹用紅連接表示2-3樹中另類的3-結點,統一了樹中的結點類型,使代碼實現簡單化,又不破壞其高效性。)

 

  • 顏色表示

由於每一個結點都只會有一條指向本身的連接(從它的父結點指向它),咱們將連接的顏色保存在表示結點的Node數據類型的布爾變量color中(若指向它的連接是紅色的,那麼該變量爲true,黑色則爲false)。

當咱們提到一個結點顏色時,咱們指的是指向該結點的連接的顏色。

 

  • 旋轉

 

在咱們實現的某些操做中可能會出現紅色右連接或者兩條連續的紅連接,但在操做完成前這些狀況都會被當心地旋轉並修復。

(咱們在這裏不討論旋轉的幾種狀況,把紅黑樹看作2-3樹,天然能夠獲得正確的旋轉後結果)

 

  • 插入

 

在插入時咱們可使用旋轉操做幫助咱們保證2-3樹和紅黑樹之間的一一對應關係,由於旋轉操做能夠保持紅黑樹的兩個重要性質:有序性和完美平衡性。

 

  • 熱身:

 

向2-結點中插入新鍵

(向紅黑樹中插入操做時,想一想2-3樹的插入操做。紅黑樹與2-3樹在本質上是相同的,只是它們對3結點的表示不一樣。

向一個只含有一個2-結點的2-3樹中插入新鍵後,2-結點變爲3-結點。咱們再把這個3-結點轉化爲紅結點便可)

 

向一顆雙鍵樹(即一個3-結點)中插入新鍵

(向紅黑樹中插入操做時,想一想2-3樹的插入操做。你把紅黑樹當作2-3樹來處理插入,一切都變得簡單了)

(向2-3樹中的一個3-結點插入新鍵,這個3結點臨時成爲4-結點,而後分裂成3個2結點)

 


★一顆紅黑樹的構造全過程

 

5.平衡二叉樹(AVL樹)


定義:平衡二叉樹(Balance Binary Tree)又稱AVL樹。它或者是一顆空樹,或者是具備下列性質的二叉樹:它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值不超過1。

若將二叉樹上結點的平衡因子BF(BalanceFactor)定義爲該結點的左子樹深度減去它的右子樹深度,則平衡因子的絕對值大於1。

 

其旋轉操做 用2-3樹的分裂來類比想象。--------------------- 原文:https://blog.csdn.net/yang_yulei/article/details/26066409 

相關文章
相關標籤/搜索