AVL(平衡二叉樹),它也是一種二分搜索樹。它的特色是每一個節點的左右子樹之差不超過1。在某種特殊的狀況下,普通的二分搜索樹可能退化爲鏈表,例如加入的元素順序爲1,2,3,4,5。這個時候查詢的效率會從O(logn)退化爲O(n)。而咱們解決這種特定的狀況就須要採用平衡二叉樹來解決這個問題。node
AVL的每一個節點的左子樹小於(大於)該節點,右子樹大於(小於)該節點,每一個節點的左右子樹的深度之差不超過1。節點的左右子樹深度之差叫平衡銀子。mysql
如圖:git
增刪改查操做和二分搜索樹相似,可是要多考慮的就是對節點的平衡考慮,若是一串數字的插入順序爲3,4,5。那麼這棵樹結構就會退化爲一個鏈表。而這時候AVL就會對這個樹進行旋轉操做來達到平衡,因此,咱們就知道旋轉的操做會在增長,刪除,修改這三個地方進行旋轉。旋轉操做分爲下面四種狀況github
如圖,8的左子樹已經退化爲鏈表,而且5,8這兩個節點再也不平衡,這時咱們先找到深度最深的不平衡節點5,對節點5進行LL旋轉操做,在如圖的這種狀況下,獲得右圖的結構sql
如圖,當插入順序爲當插入順序爲8,3,10,13,15的時候,樹的結構變成左邊的樣子,這時10節點和8節點已經不平衡,爲了保持AVL的平衡,咱們要對10節點進行RR旋轉,如右圖所示數組
如圖。5,8節點已經不平衡,這時要對5節點作平衡處理,首先將5進行RR左旋轉,7的左節點也變爲5的右節點。bash
這時7,8仍是不平衡的,對8進行右旋轉,8的右節點也變爲8的左節點,如圖。數據結構
如左圖,8,13節點不平衡,對13節點進行LL右旋轉,獲得右圖ui
這時8,10是不平衡的,對8節點進行RR左旋轉,獲得右圖。spa
以上就是保持平衡的方式。
插入操做和平衡二叉樹相似,不過在插入以後要保持樹的平衡,針對以上四種狀況保持。
刪除操做和平衡二叉樹相似,在刪除的時候當把子節點移到刪除節點位置後也可能對樹進行旋轉保持平衡。
AVL的增刪改查的平均時間複雜度都是O(logn),他比二分搜索樹的好處在於他可以對樹結構進行一個平衡,而不讓他退化爲鏈表
這裏如何判斷一個節點是否平衡呢?這裏看一下AVL中每一個節點的結構
private class Node{
public K key;
public V value;
public Node left, right;
public int height;//節點的當前高度
}
複製代碼
判斷一個節點是否平衡,即計算一個節點的平衡因子
private int getBalanceFactor(Node node){
if(node == null)
return 0;
//返回平衡因子,若是結果小於-1說明右傾;結果大於1說明左傾;結果在-1到1之間說明節點平衡
return node.left.height - node.right.height;
}
複製代碼
總的來講增長和刪除要比二分搜索樹多考慮的就是增長和刪除功能
2-3樹是一種絕對平衡樹。它的節點的元素個數能夠爲1個或2個。如圖,就是一棵2-3樹:
2-3樹中的2表明一個節點有兩個孩子,3表明一個節點有三個孩子。
這裏結合一個例子來查看2-3樹是如何實現絕對平衡的。例如,如今咱們要依次增長1,2,3,4,5,6,7這7個元素,如圖
如圖所示,下面一個步驟一個步驟的分析:
總的來講,插入方法和二分搜索樹類似。可是每一個節點能夠有1-2個元素,當節點元素個數爲3時,就會分紅3個節點並向上合併,直到合併完成。
2-3樹的查找和二分搜索樹相似,不過由於一個節點可能有2個元素,須要對這兩個元素進行比較,分別前往這個節點的左,中,右孩子繼續進行比較
2-3樹的刪除稍微複雜一點,刪除可分爲兩大狀況,就是刪除葉子節點和非葉子節點。
這裏只說理論狀況,不結合代碼實現,實際上代碼實現會變得複雜也只是由於考慮的東西更多,代碼實現會變得複雜。
使用中序遍歷下的直接後繼節點key來覆蓋當前節點key,再刪除用來覆蓋的後繼節點key
瞭解完2-3樹以後咱們能夠很輕鬆的瞭解和實現紅黑樹和B-樹
在開始紅黑樹以前,咱們要知道紅黑樹並不是只有2-3樹這一種實現方式,雖然2-3樹實現紅黑樹比較方便。事實是隻要知足5個定義的樹都是紅黑樹,如下是紅黑樹的定義:
紅黑樹的每一個節點有兩種顏色,紅色和黑色,以下面兩圖,就是兩種不一樣實現的紅黑樹,咱們能夠看到當最後葉子節點都會增長NIL讓葉子節點統一爲黑色節點,圖二隻是沒有加上最後的黑色葉子節點
實際上,以上兩個圖中第二個圖就是用2-3樹實現的紅黑樹
如圖,咱們能夠看到,能夠將2-3樹中的3節點中的左元素弄成一個新節點,這個節點就是紅黑樹中的紅節點,而且將紅節點統一進行左偏向,得出右邊的紅黑樹,這樣的紅黑樹也叫左傾紅黑樹
在瞭解了紅黑樹和2-3樹之間的關係以後咱們就能夠看紅黑樹是如何實現的了。
紅黑樹的節點結構
private class Node{
public K key;//排序也是經過key進行排序
public V value;
public Node left, right;
public boolean color;//紅爲true,黑爲false,默認節點爲紅
}
複製代碼
總的來講,對於添加一個節點,操做邏輯和2-3樹相同,不過是把2-3樹中的3節點的左元素變爲新的節點,這個節點爲紅色而且左傾。
紅黑樹要對一個插入操做進行維護,會進行左旋轉,右旋轉,顏色翻轉,以下圖
由於咱們默認新添加一個節點的時候是紅色,咱們要使節點知足上述5點紅黑樹的定義,首先,咱們須要像AVL那樣將圖2的樹形態旋轉爲3形態,再想AVL同樣右旋轉爲圖四狀態,這時雖然達到平衡可是反轉顏色(令雙親結點爲紅,孩子節點爲黑),最後將根節點變爲黑色便可。
// node x
// / \ 左旋轉 / \
// T1 x ---------> node T3
// / \ / \
// T2 T3 T1 T2
private Node leftRotate(Node node){
Node x = node.right;
// 左旋轉
node.right = x.left;
x.left = node;
x.color = node.color;
node.color = RED;
return x;
}
複製代碼
// node x
// / \ 右旋轉 / \
// x T2 -------> y node
// / \ / \
// y T1 T1 T2
private Node rightRotate(Node node){
Node x = node.left;
// 右旋轉
node.left = x.right;
x.right = node;
x.color = node.color;
node.color = RED;
return x;
}
複製代碼
// 顏色翻轉
private void flipColors(Node node){
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
複製代碼
紅黑樹並無像AVL追求平衡,他不像AVL要求每一個節點的平衡因子絕對值必須小於等於1。這樣相對於AVL來講紅黑樹的旋轉操做會更少,例如刪除,插入節點操做,AVL是要從刪除,增長節點到根節點的全部節點進行平衡旋轉(O(logn)),而紅黑樹最多隻須要3次就能夠實現平衡O(1)(雖然經過上文實現的紅黑樹並不能作到,但有實現是能夠的),因此紅黑樹更適合增刪多的場景。
因此,在增刪多的場景適合紅黑樹,查找多的場景適合AVL。
B樹和2-3樹的原理相同,B樹也能夠是2-4樹,2-M樹 關於B樹有以下的定義,若是一棵B樹有M階(層):
以下圖是一個3階B樹:
對應的這個3階樹就能夠當作2-3樹,操做也是類似的
B樹大多用在磁盤上用於查找磁盤的地址。由於磁盤會有大量的數據,有可能沒有辦法一次將須要的全部數據加入到內存中,因此只能逐一加載磁盤頁,每一個磁盤頁就對應一個節點,而對於B樹來講,B樹很好的將樹的高度下降了,這樣就會減小IO查詢次數,雖然一次加載到內存的數據變多了,但速度絕對快於AVL或是紅黑樹的。
B+樹和B樹相似,但多了幾條規則
如圖
能夠看到最主要的區別就是在葉子節點,葉子節點經過一個sqt將全部葉子節點鏈成一個鏈表,而且只有葉子節點有data域(data域就是索引指向的磁盤地址)。
B+樹和B樹的操做的優點在於B+樹的查找效率上,因此下面具體看查詢
至於其餘操做就和2-3樹類似
總結了這些樹形結構,再對mysql中innodb存儲引擎爲何使用B+樹做爲索引作個總結
咱們假設B+樹一個節點能夠有100個關鍵字,那麼3層的B樹能夠容納大概1000000多個關鍵字(100+101*100+101*101*100)。而紅黑樹要存儲這麼多至少要20層。因此使用B樹相對於紅黑樹和AVL能夠減小IO操做
鏈表查詢速度慢。數組要開闢連續空間不可能。
Innodb可以進行範圍查詢,哈希表沒法實現。例如LIKE很難用哈希表實現
B+樹只有葉子節點存放數據,而其餘節點只存放索引,而B樹每一個節點都有Data域。因此相同大小的節點B+樹包含的索引比B樹的索引更多(由於B樹每一個節點還有Data域)
還有就是B+樹的葉子節點是經過鏈表鏈接的,因此找到下限後能很快進行區間查詢,比B樹中序遍歷快
綜上所述,mysql的Innodb引擎採用的是B+樹的索引方式
最後是以前每種數據結構的實現 github.com/PatrickLee6…