LLRB——紅黑樹的現代實現

1、本文內容
以一種簡明易懂的方式介紹紅黑樹背後的邏輯實現2-3-4樹,以及紅黑樹的插入、刪除操做,重點在2-3-4樹與紅黑樹的對應關係上,並理清紅黑樹相關操做的前因後果。拋棄以往復雜的實現,而分析紅黑樹的一種簡單實現LLRB。
 
 
2、算法應用
紅黑樹,給人以強烈的第一聽覺衝擊力——紅與黑,好像很高端的感受。事實上的確如此,紅黑樹是一種高級數據結構,在C++、Java的標準庫裏做爲set、map的底層數據結構實現,以及linux中進程的公平調度。
 
 
3、2-3-4樹
標題是紅黑樹,爲何講2-3-4樹?由於紅黑樹就是2-3-4樹的一種等價形式,更準確地來講,咱們用紅黑樹來完成2-3-4樹的各類操做(如插入、刪除)。緣由就是2-3-4樹的實現即維護太麻煩。因此理解2-3-4樹才能真正理解紅黑樹。而歷史就是這麼發展的,瞭解過去,如今的一切纔有了意義。算法導論關於紅黑樹這一節就忽略了這一點,讓人知其然而不知其因此然。
 
OK,暫時先忽略複雜的紅黑樹,從簡單的2-3-4樹開始。
 
一、定義
 
2-3-4樹是一種泛化的BST,它的每一個結點容許1,2或者3個鍵(key),那麼對應的有三種結點:
2-node:一個key,兩個孩子;
3-node:二個key,三個孩子;
4-node:三個key,四個孩子。
注:k-node表示有k個連接(link)。泛化的BST還有2-3樹,B樹等。
 
從圖中能夠看出2-3-4樹的另外一個性質:它是徹底平衡的(等高),即從根結點到葉子結點距離相等。
 
二、插入操做
2-3-4樹自己就是一種查找樹(中序遍歷有序),故其查找操做同二叉查找。
 
2-3-4樹的插入操做相似二叉查找樹,先是查找操做失敗(從根結點查找到葉子結點),而後在底部的葉子結點插入。
由於2-3-4樹的結點有三種類型,因此操做有點差別。對於2-node和3-node,分別直接插入可變成3-node,4-node;可是對於4-node若直接插入則違反了定義。在4-node插入以前,先分裂4-node成2個2-node,再將待插入的key插入對應的2-node。 以下圖,H查找失敗,在H插入4-node(由三個key F、G、J組成)以前,先對該4-node分裂(將三個key的中間值提上父節點,剩餘的二個key分別做爲中間key的左右孩子),而後再將H插入2-node J中。這樣操做的結果是查找到達底部葉子結點時,始終是2-node或者3-node。
 
插入算法思想:自下而上的算法由原做者Bayer在1972年提出,自上而下的算法由Guibas-Sedgewick(紅黑樹這個名字來源於他們)在1978年提出,而後30年後也就是2008年Sedgewick教授又改進了紅黑樹的操做,也就是後面要介紹的LLRB。
 
自上而下的算法思路是,從根結點向下的查找過程當中,遇到4-node就分裂,最後在底部的葉子結點插入。
那麼爲何遇到4-node就分裂呢?4-node不是2-3-4樹的一種合法結點類型嗎?
答案能夠從後面LLRB的算法思路能夠得出。
 
由於遇到4-node就分裂就保證了當前結點不是4-node,則分裂孩子的4-node有兩種情形:
分裂4-node的case 1
 
 
分裂4-node的case 2

注:上面的變換在樹中任意位置都成立。
 
 
下面兩張圖是完整的插入過程(只有分裂結點類型爲4-node的根結點纔會致使樹高增1):
 
 
 
 
三、平衡性分析
2-3-4樹的樹高在最壞狀況下爲lgN(全部結點都是2-node型),最好狀況下爲lg4 N = 1/2 lgN(全部結點都是4-node型),2-3-4樹的查找、插入操做都是lgN。
 
 
4、紅黑樹
 
終於到了高富帥——紅黑樹。。。
從2-3-4樹的介紹能夠看出,對2-node、3-node、4-node的不一樣數據類型進行轉換,但所涉及的大部分任務使用這種 直接的表示方法來實現並不方便。因此能夠用
一種 統一的方式完成轉換,而只需很小的開銷。這就是紅黑樹存在的意義,既有BST的標準搜索過程,又有2-3-4樹的簡單插入平衡過程。
 
下面介紹LLRB(Left-leaning red-black trees),而不是標準的紅黑樹。
一、定義
LLRB有三個特色:
(1)用BST來表示2-3-4樹;
(2)用紅邊(紅連接)來鏈接2-node來表示3-node和4-node(以下圖);
(3)3-node必須是向左傾斜的(二者的大者做爲根)。
 
LLRB相對於標準的RB多了特色3,在標準的RB中右向傾斜的紅連接是容許的。對於特色2,在物理上用一個bit(紅或黑)來存儲以表示指向該結點的紅連接。
紅連接來鏈接3-node或者4-node的內部key,而黑連接則鏈接外部的key;爲了理解,能夠消除紅連接並將它們鏈接的結點都摺疊起來(即將看作紅連接連
接的點縮爲一個點),則能夠看出黑連接個數不變。
2-3-4樹與紅黑樹是一一對應的關係
 
 
且上下關係中不容許2個連續的紅邊
 
由特色3能夠推出LLRB的一個特性,紅黑樹與2-3-4樹一一對應。
 
二、插入算法
一樣地,在LLRB中查找操做同BST。
在插入以前要知道一個操做: 旋轉。它有兩種狀況:左旋,右旋。
 
 
左旋 右旋
 
插入算法思路:即前面介紹的2-3-4樹
具體實現時,插入一個結點時,始終是紅結點,即用紅邊連接該結點。對於2-node、3-node直接插入(k-node有k個插入點),如違反上面的左紅連接和連續的紅連接,則旋轉做調整。對於4-node(左右都爲紅連接),先分裂,物理實現是一個 翻轉(左右紅連接變黑,父連接變紅)。
2-node插入的兩種case
 
 
3-node插入的三種case
 
 
 
4-node分裂操做
 
 
由4-node的分裂可知黑高度不變,分裂操做即翻轉在圖片上對應爲紅連接向上傳遞。
在介紹2-3-4樹時,4-node分裂操做有兩種狀況,4-node的parent是2-node和3-node;再結合k-node有k個插入點,則總共有6種狀況。
4-node的分裂case 1
 
 
 
4-node的分裂case 2
 
 
看了上面兩幅圖後,也許會讓人以爲紅黑樹太複雜了,這麼多case,其實否則,在LLRB實現中只有兩種操做: 旋轉翻轉。旋轉的目的是保持平衡,翻轉的目的是分裂4-node。
看了下面的LLRB插入算法,你就會明白上面4-node的翻轉、旋轉實際上是分開的兩個過程(翻轉自上而下,旋轉自下而上),只是爲了統一這個完整的過程而畫在了一塊兒,纔會有那麼多case。
 
LLRB的插入算法:
首先結合2-3-4樹的插入算法思路,先自上至下查找(遇到4-node則翻轉),而後在底部葉子結點插入,由於在自上至下的過程當中,可能會產生不知足LLRB的性質的狀況,故插入結點後須要自下至上調整以恢復LLRB性質。
下圖是插入算法的核心代碼,第2是分裂即翻轉,第1是插入操做,第三、4是調整。
 
從插入算法能夠看出,若是自下而上再分裂4-node,則會出現它的parent也多是4-node,祖父結點也多是4-node;咱們能夠一直向上分裂,這也正是上面提到的自下而上的思路(原做者:Bayer);而更簡單的方法是,在沿樹向下的過程當中,遇到4-node就分裂,這也正是自上而下與自下而上的區別。
插入算法的核心代碼
 
上圖的核心代碼按照自上而下和自下而上的順序放入BST的插入(遞歸版本)操做中即獲得下圖的完整的插入算法。
注:分裂(即翻轉)是自上而下,因此放在遞歸以前;調整(即旋轉)是自下而上,因此放在遞歸以後。
完整的插入代碼
 
 
若是將分裂操做放到遞歸以後,也就是先自上而下查找,插入結點,而後自下而上調整也可一樣完成插入操做而不破壞LLRB的性質。
2-3樹的插入操做
 
 
其實上述描述的就是2-3樹的插入操做,它與2-3-4樹的插入的區別在於:2-3樹先插入,再分裂(down)、調整(up);2-3-4樹先分裂(down),再插入、調整(up)。又由於插入老是在最後一層進行,故翻轉的位置決定了對應樹的實現。
這也是爲何2-3-4樹叫top-down,而2-3樹叫bottom-up。
 
三、刪除算法
LLRB的刪除相似於插入,只不過處理恰好相反。插入、刪除都有臨界點:插入4-node,刪除2-node,對臨界點的操做都會引發突變,由於它們會破壞LLRB的性質(黑高度)。
因此同插入同樣,先從上至下查找,若是查找在3-node或4-node結束,則直接刪除;
3-node和4-node的刪除
 
 
對於2-node的刪除同4-node的插入相反,2-node的刪除是先合併2個2-node爲1個4-node,而後再安全地刪除對應的2-node中的key。
一樣地,由於parent不爲2-node(遇到即合併),再結合兄弟結點的二、三、4-node,則刪除總共有6種狀況(2-node的兄弟爲2-node, 3-node,4-node,父親爲3-node,4-node,總共2*3=6種狀況)。一樣,實際的刪除實現也沒這麼複雜。
2-node的刪除(其實合併和借都是借,2-node不能直接刪除,先合併或者借再刪。)
 
 
在介紹刪除任意一個結點時,先分析刪除樹中最小的結點。由於它是刪除任意結點的一部分,後面能夠看出來。
首先,爲了保證能夠直接刪除最小的某個結點,須要假設當前結點h或者h.left是紅色鏈。
而後從上而下查找過程當中,2個2-node要變爲1個4-node,則需反向翻轉(紅色父連接變黑,黑色子連接變紅),
爲了將紅鏈從上向左子樹傳遞(刪除紅結點,不改變黑高度),需保證h爲紅,h.left和h.left.left爲黑;
當h.left和h.left.left都爲黑時,
若是h.right.left爲紅,則要從右邊借兄弟(下圖case 2),若是h.right.left爲黑,則不須要(下圖case1)。
注:在翻轉的同時,右子樹可能會產生連續的紅鏈,則需調整。
case 1
 
 
 
case 2
 
 
 
紅鏈向左移動                   紅鏈向左移動對應的example
 
 
deleteMin的實現
 
 
 
 
deleteMin的example
 
 
完成了deleteMin就完成了LLRB的刪除操做的一大半。如今是刪除LLRB的任意一個key,
自上而下查找過程當中,左邊查找用moveRedLeft;右邊查找用moveRedRight;直到最後的底部葉子結點,直接刪除便可;一樣,自下而上調整。
 
怎樣將delete操做歸約到delteMin去呢?算法導論提供的一個技巧是:replace,deleteMin(即用後繼的key代替當前的key,再刪除右孩子的最小結點)。
刪除技巧
 
 
 
完整刪除代碼
 
 
 
參考:
《算法導論》
《algorithm in c》
 

PS:9.9憶山東兄弟,必登高望遠,一覽縱山小。node

相關文章
相關標籤/搜索