1、二叉查找樹的缺點
二叉查找樹,相信大家都接觸過,二叉查找樹的特點就是左子樹的節點值比父親節點小,而右子樹的節點值比父親節點大,如圖
基於二叉查找樹的這種特點,我們在查找某個節點的時候,可以采取類似於二分查找的思想,快速找到某個節點。n 個節點的二叉查找樹,正常的情況下,查找的時間復雜度為 O(logn)。
之所以說是正常情況下,是因為二叉查找樹有可能出現一種極端的情況,例如
這種情況也是滿足二叉查找樹的條件,然而,此時的二叉查找樹已經近似退化為一條鏈表,這樣的二叉查找樹的查找時間復雜度頓時變成了 O(n),可想而知,我們必須不能讓這種情況發生,為了解決這個問題,於是我們引申出了平衡二叉樹。
2、平衡二叉樹
平衡二叉樹就是為了解決二叉查找樹退化成一顆鏈表而誕生了,平衡樹具有如下特點
1、具有二叉查找樹的全部特性。
2、每個節點的左子樹和右子樹的高度差至多等於1。
例如:圖一就是一顆平衡樹了,而圖二則不是(節點右邊標的是這個節點的高度)
對於圖二,因為節點9的左孩子高度為2,而右孩子高度為0。他們之間的差值超過1了。
平衡樹基於這種特點就可以保證不會出現大量節點偏向於一邊的情況了。關於平衡樹如何構建、插入、刪除、左旋、右旋等操作這里不在說明.
於是,通過平衡樹,我們解決了二叉查找樹的缺點。對於有 n 個節點的平衡樹,最壞的查找時間復雜度也為 O(logn)。
3、為什么有了平衡樹還需要紅黑樹?
雖然平衡樹解決了二叉查找樹退化為近似鏈表的缺點,能夠把查找時間控制在 O(logn),不過卻不是最佳的,因為平衡樹要求每個節點的左子樹和右子樹的高度差至多等於1,這個要求實在是太嚴了,導致每次進行插入/刪除節點的時候,幾乎都會破壞平衡樹的第二個規則,進而我們都需要通過左旋和右旋來進行調整,使之再次成為一顆符合要求的平衡樹。
顯然,如果在那種插入、刪除很頻繁的場景中,平衡樹需要頻繁着進行調整,這會使平衡樹的性能大打折扣,為了解決這個問題,於是有了紅黑樹,紅黑樹具有如下特點:
1、紅黑樹的特性
(1)每個節點或者是黑色,或者是紅色。
(2)根節點是黑色。
(3)每個葉子節點(NIL)是黑色。 [注意:這里葉子節點,是指為空(NIL或NULL)的葉子節點!]
(4)如果一個節點是紅色的,則它的子節點必須是黑色的。
(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。[這里指到葉子節點的路徑]
包含n個內部節點的紅黑樹的高度是 O(log(n)).
如圖:
2、紅黑樹的使用場景
java中使用到紅黑樹的有TreeSet和JDK1.8的HashMap。
但是問題來了,為什么要使用紅黑樹,紅黑樹的插入和刪除都要滿足以上5個特性,而作非常復雜的操作。
原因:
紅黑樹是一種平衡樹,他復雜的定義和規則都是為了保證樹的平衡性。如果樹不保證他的平衡性就是下圖:很顯然這就變成一個鏈表了。
保證平衡性的最大的目的就是降低樹的高度,因為樹的查找性能取決於樹的高度。所以樹的高度越低搜索的效率越高!
這也是為什么存在二叉樹、搜索二叉樹等,各類樹的目的。
二、B樹
1、B樹的特性
一棵m階的B樹的滿足條件:
(1)每個節點至多有m棵子樹
(2)根節點除外,其它每個分支節點至少有【m/2】棵子樹
(3)根節點至少有兩棵子樹(除非B樹只包含一個節點)
(4)所有葉子節點在同一層上,B樹的葉子節點可以看成一種外部節點,不包含任何信息。
(5)有j個孩子的非葉結點恰好有j-1個關鍵碼,關鍵碼按遞增次序排列。
B 樹又叫平衡多路查找樹。如圖:
2、B樹的使用場景
B樹多用於做文件系統的索引。
那么問題來了:為什么要用B樹,紅黑樹不是就挺好的么?
原因:
B樹和二叉樹、紅黑樹相比較,子樹更多也就是路數越多,子樹月多表示數的高度越低,搜索效率越高,當然如果路數太多就可能變成一個有序數組了(如下圖)。所以當然不可能使得路數無限大。
回到正題:正因為文件系統和數據庫一般都是存在電腦硬盤上的,如果數據量太大的話不一定能一次性加載到內存中。(一棵樹不能一次性加載完怎么查找對吧?)但是B樹可以多路存儲。也正因為B樹的這一個優點,可以在文件查找的時候每次只加載一個節點的內容存入內存來查找。而紅黑樹在內存中查找非常塊,但是如果在數據庫和文件系統中,顯然B樹更優。
三、B+樹
B+樹是B樹的變種,有着比B樹更高的查詢效率。
1、B+樹的特性
(1)有 k 個子樹的中間節點包含有 k 個元素(B 樹中是 k-1 個元素),每個元素不保存數據,只用來索引,所有數據
都保存在葉子節點。
(2)所有的葉子結點中包含了全部元素的信息,及指向含這些元素記錄的指針,且葉子結點本身依關鍵字的大小
自小而大順序鏈接。
(3)所有的中間節點元素都同時存在於子節點,在子節點元素中是最大(或最小)元素。
2、B+樹的使用場景
B+樹是在B樹的基礎上進行改造的,他的數據都在葉子節點,同時葉子節點之間還加了指針形成鏈表。
B+樹多用於數據庫中的索引。
那么為什么B+樹用於數據庫中的索引呢?
原因:
因為在數據庫中select常常不只是查詢一條記錄,常常要查詢多條記錄。比如:按照id的排序的后10條。如果是多條的話,B樹需要做中序遍歷,可能要跨層訪問。而B+樹由於所有數據都在葉子結點,不用跨層,同時由於有鏈表結構,只需要找到首尾,通過鏈表就能夠把所有數據取出來了。
B*樹:
是B+樹的變體,在B+樹的非根和非葉子結點再增加指向兄弟的指針;
B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3
(代替B+樹的1/2);
B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的數據
復制到新結點,最后在父結點中增加新結點的指針;B+樹的分裂只影響原結點和父
結點,而不會影響兄弟結點,所以它不需要指向兄弟的指針;
B*樹的分裂:當一個結點滿時,如果它的下一個兄弟結點未滿,那么將一部分
數據移到兄弟結點中,再在原結點插入關鍵字,最后修改父結點中兄弟結點的關鍵字
(因為兄弟結點的關鍵字范圍改變了);如果兄弟也滿了,則在原結點與兄弟結點之
間增加新結點,並各復制1/3的數據到新結點,最后在父結點增加新結點的指針;
所以,B*樹分配新結點的概率比B+樹要低,空間使用率更高;
小結:
B-樹:
多路搜索樹,每個結點存儲M/2到M個關鍵字,非葉子結點存儲指向關鍵
字范圍的子結點;
所有關鍵字在整顆樹中出現,且只出現一次,非葉子結點可以命中;
B+樹:
在B-樹基礎上,為葉子結點增加鏈表指針,所有關鍵字都在葉子結點
中出現,非葉子結點作為葉子結點的索引;B+樹總是到葉子結點才命中;
B*樹:
在B+樹基礎上,為非葉子結點也增加鏈表指針,將結點的最低利用率
從1/2提高到2/3;
參考鏈接:https://blog.csdn.net/wyqwilliam/article/details/82935922
參考鏈接:https://blog.csdn.net/dreamispossible/article/details/92852943
參考鏈接:https://blog.csdn.net/zgz15515397650/article/details/85165454