B樹、B+樹詳解 - Assassinの - 博客園 (cnblogs.com)
B樹
前言
首先,為什么要總結B樹、B+樹的知識呢?最近在學習數據庫索引調優相關知識,數據庫系統普遍采用B-/+Tree作為索引結構(例如mysql的InnoDB引擎使用的B+樹),理解不透徹B樹,則無法理解數據庫的索引機制;接下來將用最簡潔直白的內容來了解B樹、B+樹的數據結構
另外,B-樹,即為B樹。因為B樹的原英文名稱為B-tree,而國內很多人喜歡把B-tree譯作B-樹,其實,這是個非常不好的直譯,很容易讓人產生誤解。如人們可能會以為B-樹是一種樹,而B樹又是一種樹。而事實上是,B-tree就是指的B樹,目前理解B的意思為平衡
B樹的出現是為了彌合不同的存儲級別之間的訪問速度上的巨大差異,實現高效的 I/O。平衡二叉樹的查找效率是非常高的,並可以通過降低樹的深度來提高查找的效率。但是當數據量非常大,樹的存儲的元素數量是有限的,這樣會導致二叉查找樹結構由於樹的深度過大而造成磁盤I/O讀寫過於頻繁,進而導致查詢效率低下。另外數據量過大會導致內存空間不夠容納平衡二叉樹所有結點的情況。B樹是解決這個問題的很好的結構
概念
首先,B樹不要和二叉樹混淆,在計算機科學中,B樹是一種自平衡樹數據結構,它維護有序數據並允許以對數時間進行搜索,順序訪問,插入和刪除。B樹是二叉搜索樹的一般化,因為節點可以有兩個以上的子節點。[1]與其他自平衡二進制搜索樹不同,B樹非常適合讀取和寫入相對較大的數據塊(如光盤)的存儲系統。它通常用於數據庫和文件系統。
定義
B樹是一種平衡的多分樹,通常我們說m階的B樹,它必須滿足如下條件:
- 每個節點最多只有m個子節點。
- 每個非葉子節點(除了根)具有至少⌈ m/2⌉子節點,含有ceil(m/2)-1到m-1個元素。
- 如果根不是葉節點,則根至少有兩個子節點。
- 具有k個子節點的非葉節點包含k -1個鍵。
- 所有葉子都出現在同一水平,沒有任何信息(高度一致)。
第一次看到這個定義的時候,在想什么鬼?。。。。什么是階?子節點、飛葉子點、根???啥意思!少年別慌。。。
什么是B樹的階 ?
B樹中一個節點的子節點數目的最大值,用m表示,假如最大值為10,則為10階,如圖
所有節點中,節點【13,16,19】擁有的子節點數目最多,四個子節點(灰色節點),所以可以定義上面的圖片為4階B樹,現在懂什么是階了吧
什么是根節點 ?
節點【10】即為根節點,特征:根節點擁有的子節點數量的上限和內部節點相同,如果根節點不是樹中唯一節點的話,至少有倆個子節點(不然就變成單支了)。在m階B樹中(根節點非樹中唯一節點),那么根結點有關系式2<= M <=m,M為子節點數量;包含的元素數量 1<= K <=m-1,K為元素數量。
什么是內部節點 ?
節點【13,16,19】、節點【3,6】都為內部節點,特征:內部節點是除葉子節點和根節點之外的所有節點,擁有父節點和子節點。假定m階B樹的內部節點的子節點數量為M,則一定要符合(m/2)<= M <=m關系式,包含元素數量M-1;包含的元素數量 (m/2)-1<= K <=m-1,K為元素數量。m/2向上取整。
什么是葉子節點?
節點【1,2】、節點【11,12】等最后一層都為葉子節點,葉子節點對元素的數量有相同的限制,但是沒有子節點,也沒有指向子節點的指針。特征:在m階B樹中葉子節點的元素符合(m/2)-1<= K <=m-1。
好了,概念已經清楚,不用着急背公式, 接着往下看
插入
針對m階高度h的B樹,插入一個元素時,首先在B樹中是否存在,如果不存在,即在葉子結點處結束,然后在葉子結點中插入該新的元素。
- 若該節點元素個數小於m-1,直接插入;
- 若該節點元素個數等於m-1,引起節點分裂;以該節點中間元素為分界,取中間元素(偶數個數,中間兩個隨機選取)插入到父節點中;
- 重復上面動作,直到所有節點符合B樹的規則;最壞的情況一直分裂到根節點,生成新的根節點,高度增加1;
上面三段話為插入動作的核心,接下來以5階B樹為例,詳細講解插入的動作;
5階B樹關鍵點:
- 2<=根節點子節點個數<=5
- 3<=內節點子節點個數<=5
- 1<=根節點元素個數<=4
- 2<=非根節點元素個數<=4
插入8
圖(1)插入元素【8】后變為圖(2),此時根節點元素個數為5,不符合 1<=根節點元素個數<=4,進行分裂(真實情況是先分裂,然后插入元素,這里是為了直觀而先插入元素,下面的操作都一樣,不再贅述),取節點中間元素【7】,加入到父節點,左右分裂為2個節點,如圖(3)
接着插入元素【5】,【11】,【17】時,不需要任何分裂操作,如圖(4)
插入元素【13】
節點元素超出最大數量,進行分裂,提取中間元素【13】,插入到父節點當中,如圖(6)
接着插入元素【6】,【12】,【20】,【23】時,不需要任何分裂操作,如圖(7)
插入【26】時,最右的葉子結點空間滿了,需要進行分裂操作,中間元素【20】上移到父節點中,注意通過上移中間元素,樹最終還是保持平衡,分裂結果的結點存在2個關鍵字元素。
插入【4】時,導致最左邊的葉子結點被分裂,【4】恰好也是中間元素,上移到父節點中,然后元素【16】,【18】,【24】,【25】陸續插入不需要任何分裂操作
最后,當插入【19】時,含有【14】,【16】,【17】,【18】的結點需要分裂,把中間元素【17】上移到父節點中,但是情況來了,父節點中空間已經滿了,所以也要進行分裂,將父節點中的中間元素【13】上移到新形成的根結點中,這樣具體插入操作的完成。
刪除
首先查找B樹中需刪除的元素,如果該元素在B樹中存在,則將該元素在其結點中進行刪除;刪除該元素后,首先判斷該元素是否有左右孩子結點,如果有,則上移孩子結點中的某相近元素(“左孩子最右邊的節點”或“右孩子最左邊的節點”)到父節點中,然后是移動之后的情況;如果沒有,直接刪除。
- 某結點中元素數目小於(m/2)-1,(m/2)向上取整,則需要看其某相鄰兄弟結點是否豐滿;
- 如果豐滿(結點中元素個數大於(m/2)-1),則向父節點借一個元素來滿足條件;
- 如果其相鄰兄弟都不豐滿,即其結點數目等於(m/2)-1,則該結點與其相鄰的某一兄弟結點進行“合並”成一個結點;
接下來還以5階B樹為例,詳細講解刪除的動作;
- 關鍵要領,元素個數小於 2(m/2 -1)就合並,大於4(m-1)就分裂
如圖依次刪除依次刪除【8】,【20】,【18】,【5】
首先刪除元素【8】,當然首先查找【8】,【8】在一個葉子結點中,刪除后該葉子結點元素個數為2,符合B樹規則,操作很簡單,咱們只需要移動【11】至原來【8】的位置,移動【12】至【11】的位置(也就是結點中刪除元素后面的元素向前移動)
下一步,刪除【20】,因為【20】沒有在葉子結點中,而是在中間結點中找到,咱們發現他的繼承者【23】(字母升序的下個元素),將【23】上移到【20】的位置,然后將孩子結點中的【23】進行刪除,這里恰好刪除后,該孩子結點中元素個數大於2,無需進行合並操作。
下一步刪除【18】,【18】在葉子結點中,但是該結點中元素數目為2,刪除導致只有1個元素,已經小於最小元素數目2,而由前面我們已經知道:如果其某個相鄰兄弟結點中比較豐滿(元素個數大於ceil(5/2)-1=2),則可以向父結點借一個元素,然后將最豐滿的相鄰兄弟結點中上移最后或最前一個元素到父節點中,在這個實例中,右相鄰兄弟結點中比較豐滿(3個元素大於2),所以先向父節點借一個元素【23】下移到該葉子結點中,代替原來【19】的位置,【19】前移;然【24】在相鄰右兄弟結點中上移到父結點中,最后在相鄰右兄弟結點中刪除【24】,后面元素前移。
最后一步刪除【5】, 刪除后會導致很多問題,因為【5】所在的結點數目剛好達標,剛好滿足最小元素個數(ceil(5/2)-1=2),而相鄰的兄弟結點也是同樣的情況,刪除一個元素都不能滿足條件,所以需要該節點與某相鄰兄弟結點進行合並操作;首先移動父結點中的元素(該元素在兩個需要合並的兩個結點元素之間)下移到其子結點中,然后將這兩個結點進行合並成一個結點。所以在該實例中,咱們首先將父節點中的元素【4】下移到已經刪除【5】而只有【6】的結點中,然后將含有【4】和【6】的結點和含有【1】,【3】的相鄰兄弟結點進行合並成一個結點。
也許你認為這樣刪除操作已經結束了,其實不然,在看看上圖,對於這種特殊情況,你立即會發現父節點只包含一個元素【7】,沒達標(因為非根節點包括葉子結點的元素K必須滿足於2=<K<=4,而此處的K=1),這是不能夠接受的。如果這個問題結點的相鄰兄弟比較豐滿,則可以向父結點借一個元素。而此時兄弟節點元素剛好為2,剛剛滿足,只能進行合並,而根結點中的唯一元素【13】下移到子結點,這樣,樹的高度減少一層。
看完插入,刪除,想必也把B樹的特征掌握了,下面普及下其他知識,換個腦子
磁盤IO與預讀
計算機存儲設備一般分為兩種:內存儲器(main memory)和外存儲器(external memory)。
內存儲器為內存,內存存取速度快,但容量小,價格昂貴,而且不能長期保存數據(在不通電情況下數據會消失)。
外存儲器即為磁盤讀取,磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間可以分為尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所需要的時間,主流磁盤一般在5ms以下;旋轉延遲就是我們經常聽說的磁盤轉速,比如一個磁盤7200轉,表示每分鍾能轉7200次,也就是說1秒鍾能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,一般在零點幾毫秒,相對於前兩個時間可以忽略不計。那么訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一台500 -MIPS的機器每秒可以執行5億條指令,因為指令依靠的是電的性質,換句話說執行一次IO的時間可以執行40萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。下圖是計算機硬件延遲的對比圖,供大家參考:
考慮到磁盤IO是非常高昂的操作,計算機操作系統做了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩沖區內,因為局部預讀性原理告訴我們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據我們稱之為一頁(page)。具體一頁有多大數據跟操作系統有關,一般為4k或8k,也就是我們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計非常有幫助。
事實1 : 不同容量的存儲器,訪問速度差異懸殊。
- 磁盤(ms級別) << 內存(ns級別), 100000倍
- 若內存訪問需要1s,則一次外存訪問需要一天
- 為了避免1次外存訪問,寧願訪問內存100次...所以將
最常用
的數據存儲在最快的存儲器中
事實2 : 從磁盤中讀 1 B,與讀寫 1KB 的時間成本幾乎一樣
從以上數據中可以總結出一個道理,索引查詢的數據主要受限於硬盤的I/O速度,查詢I/O次數越少,速度越快,所以B樹的結構才應需求而生;B樹的每個節點的元素可以視為一次I/O讀取,樹的高度表示最多的I/O次數,在相同數量的總元素個數下,每個節點的元素個數越多,高度越低,查詢所需的I/O次數越少;假設,一次硬盤一次I/O數據為8K,索引用int(4字節)類型數據建立,理論上一個節點最多可以為2000個元素,2000*2000*2000=8000000000,80億條的數據只需3次I/O(理論值),可想而知,B樹做為索引的查詢效率有多高;
另外也可以看出同樣的總元素個數,查詢效率和樹的高度密切相關
B樹的高度
一棵含有N個總關鍵字數的m階的B樹的最大高度是多少?
log(m/2)(N+1)/2 + 1 ,log以(m/2)為低,(N+1)/2的對數再加1
算法如下
B+樹
B+樹是應文件系統所需而產生的B樹的變形樹,那么可能一定會想到,既然有了B樹,又出一個B+樹,那B+樹必然是有很多優點的
B+樹的特征:
- 有m個子樹的中間節點包含有m個元素(B樹中是k-1個元素),每個元素不保存數據,只用來索引;
- 所有的葉子結點中包含了全部關鍵字的信息,及指向含有這些關鍵字記錄的指針,且葉子結點本身依關鍵字的大小自小而大的順序鏈接。 (而B 樹的葉子節點並沒有包括全部需要查找的信息);
- 所有的非終端結點可以看成是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵字。 (而B 樹的非終節點也包含需要查找的有效信息);
為什么說B+樹比B樹更適合數據庫索引?
1)B+樹的磁盤讀寫代價更低
B+樹的內部結點並沒有指向關鍵字具體信息的指針。因此其內部結點相對B 樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那么盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的需要查找的關鍵字也就越多。相對來說IO讀寫次數也就降低了;
2)B+樹查詢效率更加穩定
由於非終結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當;
3)B+樹便於范圍查詢(最重要的原因,范圍查找是數據庫的常態)
B樹在提高了IO性能的同時並沒有解決元素遍歷的我效率低下的問題,正是為了解決這個問題,B+樹應用而生。B+樹只需要去遍歷葉子節點就可以實現整棵樹的遍歷。而且在數據庫中基於范圍的查詢是非常頻繁的,而B樹不支持這樣的操作或者說效率太低;不懂可以看看這篇解讀-》范圍查找
補充:B樹的范圍查找用的是中序遍歷,而B+樹用的是在鏈表上遍歷;
B+樹如下:
B+樹介紹 - wade&luffy - 博客園 (cnblogs.com)
B+樹
B+樹和二叉樹、平衡二叉樹一樣,都是經典的數據結構。B+樹由B樹和索引順序訪問方法(ISAM,是不是很熟悉?對,這也是MyISAM引擎最初參考的數據結構)演化而來,但是在實際使用過程中幾乎已經沒有使用B樹的情況了。
B+樹的定義十分復雜,因此只簡要地介紹B+樹:B+樹是為磁盤或其他直接存取輔助設備而設計的一種平衡查找樹,在B+樹中,所有記錄節點都是按鍵值的大小順序存放在同一層的葉節點中,各葉節點指針進行連接。
我們先來看一個B+樹,其高度為2,每頁可存放4條記錄,扇出(fan out)為5。
可以看出,所有記錄都在葉節點中,並且是順序存放的,如果我們從最左邊的葉節點開始順序遍歷,可以得到所有鍵值的順序排序:5、10、15、20、25、30、50、55、60、65、75、80、85、90。
B+樹的插入操作
B+樹的插入必須保證插入后葉節點中的記錄依然排序,同時需要考慮插入B+樹的三種情況,每種情況都可能會導致不同的插入算法,如表5-1所示。
我們用實例來分析B+樹的插入,我們插入28這個鍵值,發現當前Leaf Page和Index Page都沒有滿,我們直接插入就可以了。
這次我們再插入一條70這個鍵值,這時原先的Leaf Page已經滿了,但是Index Page還沒有滿,符合表5-1的第二種情況,這時插入Leaf Page后的情況為50、55、60、65、70。我們根據中間的值60拆分葉節點。
因為圖片顯示的關系,這次我沒有能在各葉節點加上雙向鏈表指針。最后我們來插入記錄95,這時符合表5-1討論的第三種情況,即Leaf Page和Index Page都滿了,這時需要做兩次拆分。
可以看到,不管怎么變化,B+樹總是會保持平衡。但是為了保持平衡,對於新插入的鍵值可能需要做大量的拆分頁(split)操作,而B+樹主要用於磁盤,因此頁的拆分意味着磁盤的操作,應該在可能的情況下盡量減少頁的拆分。因此,B+樹提供了旋轉(rotation)的功能。
旋轉發生在Leaf Page已經滿了、但是其左右兄弟節點沒有滿的情況下。這時B+樹並不會急於去做拆分頁的操作,而是將記錄移到所在頁的兄弟節點上。通常情況下,左兄弟被首先檢查用來做旋轉操作,這時我們插入鍵值70,其實B+樹並不會急於去拆分葉節點,而是做旋轉,50,55,55旋轉。
可以看到,采用旋轉操作使B+樹減少了一次頁的拆分操作,而這時B+樹的高度依然還是2。
B+樹的刪除操作
B+樹使用填充因子(fill factor)來控制樹的刪除變化,50%是填充因子可設的最小值。B+樹的刪除操作同樣必須保證刪除后葉節點中的記錄依然排序,同插入一樣,B+樹的刪除操作同樣需要考慮如表5-2所示的三種情況,與插入不同的是,刪除根據填充因子的變化來衡量。
首先,刪除鍵值為70的這條記錄,該記錄符合表5-2討論的第一種情況,刪除后。
接着我們刪除鍵值為25的記錄,這也是表5-2討論的第一種情況,但是該值還是Index Page中的值,因此在刪除Leaf Page中25的值后,還應將25的右兄弟節點的28更新到Page Index中,最后可得到圖。
最后我們來看刪除鍵值為60的情況,刪除Leaf Page中鍵值為60的記錄后,填充因子小於50%,這時需要做合並操作,同樣,在刪除Index Page中相關記錄后需要做Index Page的合並操作,最后得到圖。
紅黑樹,超強動靜圖詳解,簡單易懂 - 知乎 (zhihu.com)
紅黑樹
紅黑樹,Red-Black Tree 「RBT」是一個自平衡(不是絕對的平衡)的二叉查找樹(BST),樹上的每個節點都遵循下面的規則:
- 每個節點都有紅色或黑色
- 樹的根始終是黑色的 (黑土地孕育黑樹根, )
- 沒有兩個相鄰的紅色節點(紅色節點不能有紅色父節點或紅色子節點,並沒有說不能出現連續的黑色節點)
- 從節點(包括根)到其任何后代NULL節點(葉子結點下方掛的兩個空節點,並且認為他們是黑色的)的每條路徑都具有相同數量的黑色節點
瞬間懵逼?了解一下印象就行,開始玩魔方都是要照着魔方公式一點點玩的,多玩幾次就熟悉了。紅黑樹也一樣,紅黑樹有兩大操作:
- recolor (重新標記黑色或紅色)
- rotation (旋轉,這是樹達到平衡的關鍵)
二. 紅黑樹規則特點
紅黑樹具體有哪些規則特點呢?
- 節點分為紅色或者黑色;
- 根節點必為黑色;
- 葉子節點都為黑色,且為null;
- 連接紅色節點的兩個子節點都為黑色(紅黑樹不會出現相鄰的紅色節點);
- 從任意節點出發,到其每個葉子節點的路徑中包含相同數量的黑色節點;
- 新加入到紅黑樹的節點為紅色節點;
規則看着好像挺多,沒錯,因為紅黑樹也是均衡二叉樹,需要具備自動維持平衡的性質,上面的6條就是紅黑樹給出的自動維持平衡所需要具備的規則
我們看一看一個典型的紅黑樹到底是什么樣兒?
首先解讀一下規則,除了字面上看到的意思,還隱藏了哪些意思呢?
第一. 從根節點到葉子節點的最長路徑不大於最短路徑的2倍
怎么樣的路徑算最短路徑?
從規則5中,我們知道從根節點到每個葉子節點的黑色節點數量是一樣的,那么純由黑色節點組成的路徑就是最短路徑;
什么樣的路徑算是最長路徑?
根據規則4和規則3,若有紅色節點,則必然有一個連接的黑色節點,當紅色節點和黑色節點數量相同時,就是最長路徑,也就是黑色節點(或紅色節點)* 2
第二. 為什么說新加入到紅黑樹中的節點為紅色節點
從規則4中知道,當前紅黑樹中從根節點到每個葉子節點的黑色節點數量是一樣的,此時假如新的黑色節點的話,必然破壞規則,但加入紅色節點卻不一定,除非其父節點就是紅色節點,因此加入紅色節點,破壞規則的可能性小一些,下面我們也會舉例來說明。
什么情況下,紅黑樹的結構會被破壞呢?破壞后又怎么維持平衡,維持平衡主要通過兩種方式【變色】和【旋轉】,【旋轉】又分【左旋】和【右旋】,兩種方式可相互結合。
我們會先嘗試 recolor,如果 recolor 不能達到紅黑樹的 4 點要求,然后我們嘗試 rotation,其實紅黑樹的關鍵玩法就是弄清楚 recolor 和 rotation 的規則,接下來看看詳細的算法公式吧 千萬別着急記憶公式,有圖示會逐步說明,就像魔方一樣,多玩幾次就懂了:
假設我們插入的新節點為 X
- 將新插入的節點標記為紅色
- 如果 X 是根結點(root),則標記為黑色
- 如果 X 的 parent 不是黑色,同時 X 也不是 root:
- 3.1 如果 X 的 uncle (叔叔) 是紅色
- 3.1.1 將 parent 和 uncle 標記為黑色
- 3.1.2 將 grand parent (祖父) 標記為紅色
- 3.1.3 讓 X 節點的顏色與 X 祖父的顏色相同,然后重復步驟 2、3
話不多說,看下圖
跟着上面的公式走:
- 將新插入的 X 節點標記為紅色
- 發現 X 的 parent (P) 同樣為紅色,這違反了紅黑樹的第三條規則「不能有兩個連續相鄰的紅色節點」
- 發現 X 的 uncle (U) 同樣為紅色
- 將 P 和 U 標記為黑色
- 將 X 和 X 的 grand parent (G) 標記為相同的顏色,即紅色,繼續重復公式 2、3
- 發現 G 是根結點,標記為黑色
- 結束
剛剛說了 X 的 uncle 是紅色的情況,接下來要說是黑色的情況
- 如果 X 的 parent 不是黑色,同時 X 也不是 root:
- 3.2 如果 X 的 uncle (叔叔) 是黑色,我們要分四種情況處理
- 3.2.1 左左 (P 是 G 的左孩子,並且 X 是 P 的左孩子)
- 3.2.2 左右 (P 是 G 的左孩子,並且 X 是 P 的右孩子)
- 3.2.3 右右 (和 3.2.1 鏡像過來,恰好相反)
- 3.2.4 右左 (和 3.2.2 鏡像過來,恰好相反)
當出現 uncle 是黑色的時候我們第一步要考慮的是 旋轉 ,這里先請小伙伴不要關注紅黑樹的第 4 條規則,主要是為了演示如何旋轉的,來一點點看,不要看圖就慌,有解釋的 :
左左情況
這種情況很簡單,想象這是一根繩子,手提起 P 節點,然后變色即可
左右
左旋: 使 X 的父節點 P 被 X 取代,同時父節點 P 成為 X 的左孩子,然后再應用 左左情況
右右
與左左情況一樣,想象成一根繩子
右左
右旋: 使 X 的父節點 P 被 X 取代,同時父節點 P 成為 X 的右孩子,然后再應用 右右情況
你說的動圖在哪里,你個大騙子,別着急,現在就為小伙伴們奉上動圖演示,來說明公式的使用:
案例一
插入 10,20,30,15 到一個空樹中
- 向空樹中第一次插入數字 10,肯定是 root 節點
- root 節點標記成黑色

- 向樹中插入新節點 20,標記為紅色
- 20 > 10,並發現 10 沒有葉子節點,將新節點 20 作為 10 的右孩子

- 向樹中插入新節點 30,標記為紅色
- 30 > 10,查找 10 的右子樹,找到 20
- 30 > 20,繼續查找 20 的右子樹,發現 20 沒有葉子節點,將值插在此處
- 30 和 20 節點都為紅色,30 為右孩子,20 也為右孩子,觸發了 右右情況
- 通過一次旋轉,提起 20 節點
- 20 節點是根結點,標記為黑色

- 向樹中插入新節點 15,標記為紅色
- 通過比對大小和判斷是否有葉子節點,最終插值為 10 節點的右孩子
- 15 和 10 節點都為紅色,15 的 uncle 節點 30 也為紅色
- 按照公式,將 15 的 parent 10 和 uncle 30 更改為黑色
- 讓 15 節點 grand parent 20 的顏色與 15 節點的顏色一樣,變為紅色
- 20 為根結點,將其改為黑色

繼續插入其他節點只不過反復應用上面的公式,上面應用到的紅黑樹工具,可以暫停動畫效果,一幀一幀的看紅黑樹的轉換過程,這樣通過練習,查看公式,觀察變化三管齊下,紅黑樹的入門理解應該完全不再是問題了
四. 紅黑樹節點刪除
相比較於紅黑樹的節點插入,刪除節點更為復雜,我們從子節點是否為null和紅色為思考維度來討論。
4.1 子節點至少有一個為null
當待刪除的節點的子節點至少有一個為null節點時,刪除了該節點后,將其有值的節點取代當前節點即可,若都為null,則將當前節點設置為null,當然如果違反規則了,則按需調整,如【變色】以及【旋轉】。
4.2 子節點都是非null節點
這種情況下,
第一步:找到該節點的前驅或者后繼
前驅:左子樹中值最大的節點(可得出其最多只有一個非null子節點,可能都為null);
后繼:右子樹中值最小的節點(可得出其最多只有一個非null子節點,可能都為null);
前驅和后繼都是值最接近該節點值的節點,類似於該節點.prev = 前驅,該節點.next = 后繼。
第二步:將前驅或者后繼的值復制到該節點中,然后刪掉前驅或者后繼
如果刪除的是左節點,則將前驅的值復制到該節點中,然后刪除前驅;如果刪除的是右節點,則將后繼的值復制到該節點中,然后刪除后繼;
這相當於是一種“取巧”的方法,我們刪除節點的目的是使該節點的值在紅黑樹上不存在,因此專注於該目的,我們並不關注刪除節點時是否真是我們想刪除的那個節點,同時我們也不需考慮樹結構的變化,因為樹的結構本身就會因為自動平衡機制而經常進行調整。
前面我們已經說了,我們要刪除的實際上是前驅或者后繼,因此我們就以前驅為主線來講解,后繼的學習可參考前驅,包括幾種情況
4.2.1 前驅為黑色節點,並且有一個非null子節點
分析:
因為要刪除的是左節點64,找到該節點的前驅63;
然后用前驅的值63替換待刪除節點的值64,此時兩個節點(待刪除節點和前驅)的值都為63;
刪除前驅63,此時成為上圖過程中間環節,但我們發現其不符合紅黑樹規則4,因此需要進行自動平衡調整;
這里直接通過【變色】即可完成。
4.2.2 前驅為黑色節點,同時子節點都為null
分析:
因為要刪除的是左節點64,找到該節點的前驅63;
然后用前驅的值63替換待刪除節點的值64,此時兩個節點(待刪除節點和前驅)的值都為63;
刪除前驅63,此時成為上圖過程中間環節,但我們發現其不符合紅黑樹規則5,因此需要進行自動平衡調整;
這里直接通過【變色】即可完成。
4.2.3 前驅為紅色節點,同時子節點都為null
分析:
因為要刪除的是左節點64,找到該節點的前驅63;
然后用前驅的值63替換待刪除節點的值64,此時兩個節點(待刪除節點和前驅)的值都為63;
刪除前驅63,樹的結構並沒有打破規則。
4.3 紅黑樹刪除總結
紅黑樹刪除的情況比較多,但也就存在以下情況:
刪除的是根節點,則直接將根節點置為null;
待刪除節點的左右子節點都為null,刪除時將該節點置為null;
待刪除節點的左右子節點有一個有值,則用有值的節點替換該節點即可;
待刪除節點的左右子節點都不為null,則找前驅或者后繼,將前驅或者后繼的值復制到該節點中,然后刪除前驅或者后繼;
節點刪除后可能會造成紅黑樹的不平衡,這時我們需通過【變色】+【旋轉】的方式來調整,使之平衡,上面也給出了例子,建議大家多多練習,而不必背下來。
B*樹
是B+樹的變體,在B+樹的非根和非葉子結點再增加指向兄弟的指針;
B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3
(代替B+樹的1/2);
B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的數據
復制到新結點,最后在父結點中增加新結點的指針;B+樹的分裂只影響原結點和父
結點,而不會影響兄弟結點,所以它不需要指向兄弟的指針;
B*樹的分裂:當一個結點滿時,如果它的下一個兄弟結點未滿,那么將一部分
數據移到兄弟結點中,再在原結點插入關鍵字,最后修改父結點中兄弟結點的關鍵字
(因為兄弟結點的關鍵字范圍改變了);如果兄弟也滿了,則在原結點與兄弟結點之
間增加新結點,並各復制1/3的數據到新結點,最后在父結點增加新結點的指針;
所以,B*樹分配新結點的概率比B+樹要低,空間使用率更高;
面試題:紅黑樹相比於BST和AVL樹有什么優點
紅黑樹是犧牲了嚴格的高度平衡的優越條件為代價,它只要求部分地達到平衡要求,降低了對旋轉的要求,從而提高了性能。
紅黑樹能夠以O(log n)的時間復雜度進行搜索、插入、刪除操作。此外,由於它的設計,任何不平衡都會在三次旋轉之內解決。
當然,還有一些更好的,但實現起來更復雜的數據結構能夠做到一步旋轉之內達到平衡,但紅黑樹能夠給我們一個比較“便宜”的解決方案。
相比於BST,因為紅黑樹可以能確保樹的最長路徑不大於兩倍的最短路徑的長度,所以可以看出它的查找效果是有最低保證的。在最壞的情況下也可以保證O(logN)的,這是要好於二叉查找樹的。因為二叉查找樹最壞情況可以讓查找達到O(N)。紅黑樹的算法時間復雜度和AVL相同,但統計性能比AVL樹更高,所以在插入和刪除中所做的后期維護操作肯定會比紅黑樹要耗時好多,但是他們的查找效率都是O(logN),所以紅黑樹應用還是高於AVL樹的. 實際上插入 AVL 樹和紅黑樹的速度取決於你所插入的數據。如果你的數據分布較好,則比較宜於采用 AVL樹(例如隨機產生系列數),但是如果你想處理比較雜亂的情況,則紅黑樹是比較快的。為什么要要用紅黑樹?
1、首先紅黑樹是不符合AVL樹的平衡條件的,即每個節點的左子樹和右子樹的高度最多差1的二叉查找樹。但是提出了為節點增加顏色,紅黑樹是用非嚴格的平衡來換取增刪節點時候旋轉次數的降低,任何不平衡都會在三次旋轉之內解決,而AVL是嚴格平衡樹,因此在增加或者刪除節點的時候,根據不同情況,旋轉的次數比紅黑樹要多。所以紅黑樹的插入效率更高
(更多相關面試題推薦:java面試題及答案)
2、紅黑樹能夠以O(log2 (n)) 的時間復雜度進行搜索、插入、刪除操作
3、簡單來說紅黑樹就是為了解決二叉查找樹的缺陷,因為二叉查找樹在某些情況下會退化成一個線性結構。
紅黑樹和平衡樹的對比和選擇
1、平衡樹結構更加直觀,讀取性能比紅黑樹要高;增加和刪除節點 恢復平衡的性能不如紅黑樹
2、紅黑樹,讀取性能不如平衡樹;增加和刪除節點 恢復平衡性能比平衡樹好
紅黑樹的使用場景:
TreeMap、TreeSet以及JDK1.8之后的HashMap底層都用到了紅黑樹五. 總結
本文主要介紹了紅黑樹的相關原理,首先紅黑樹的基礎二叉搜索樹,我們先簡單說了一下二叉搜索樹,並且講了一下搜索的流程,然后就針對紅黑樹的6大規則特點,紅黑樹的插入操作,刪除操作,都使用了大量的圖形來加以說明,技術都是練出來的,有時候很多似是而非的地方,當動筆去寫的時候,其實很好理解。紅黑樹的使用非常廣泛,如TreeMap和TreeSet都是基於紅黑樹實現的,而Jdk8中HashMap當鏈表長度大於8時也會轉化為紅黑樹,紅黑樹比較復雜,本人也是還在學習過程中,如果有不對的地方請批評指正,望共同進步謝謝。