樹形數據結構總結二(AVL,2-3樹,紅黑樹,B樹,B+樹)

AVL

AVL(平衡二叉樹),它也是一種二分搜索樹。它的特色是每一個節點的左右子樹之差不超過1。在某種特殊的狀況下,普通的二分搜索樹可能退化爲鏈表,例如加入的元素順序爲1,2,3,4,5。這個時候查詢的效率會從O(logn)退化爲O(n)。而咱們解決這種特定的狀況就須要採用平衡二叉樹來解決這個問題。node

AVL的定義

AVL的每一個節點的左子樹小於(大於)該節點,右子樹大於(小於)該節點,每一個節點的左右子樹的深度之差不超過1。節點的左右子樹深度之差叫平衡銀子mysql

如圖:git

image

AVL的操做

增刪改查操做和二分搜索樹相似,可是要多考慮的就是對節點的平衡考慮,若是一串數字的插入順序爲3,4,5。那麼這棵樹結構就會退化爲一個鏈表。而這時候AVL就會對這個樹進行旋轉操做來達到平衡,因此,咱們就知道旋轉的操做會在增長,刪除,修改這三個地方進行旋轉。旋轉操做分爲下面四種狀況github

LL右單旋轉

image

如圖,8的左子樹已經退化爲鏈表,而且5,8這兩個節點再也不平衡,這時咱們先找到深度最深的不平衡節點5,對節點5進行LL旋轉操做,在如圖的這種狀況下,獲得右圖的結構sql

RR左單旋轉

image

如圖,當插入順序爲當插入順序爲8,3,10,13,15的時候,樹的結構變成左邊的樣子,這時10節點和8節點已經不平衡,爲了保持AVL的平衡,咱們要對10節點進行RR旋轉,如右圖所示數組

LR先左後右

image

如圖。5,8節點已經不平衡,這時要對5節點作平衡處理,首先將5進行RR左旋轉,7的左節點也變爲5的右節點。bash

image

這時7,8仍是不平衡的,對8進行右旋轉,8的右節點也變爲8的左節點,如圖。數據結構

RL先右後左

image

如左圖,8,13節點不平衡,對13節點進行LL右旋轉,獲得右圖ui

image

這時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樹

2-3樹是一種絕對平衡樹。它的節點的元素個數能夠爲1個或2個。如圖,就是一棵2-3樹:

image

2-3樹中的2表明一個節點有兩個孩子,3表明一個節點有三個孩子。

2-3樹的操做

插入

這裏結合一個例子來查看2-3樹是如何實現絕對平衡的。例如,如今咱們要依次增長1,2,3,4,5,6,7這7個元素,如圖

image

如圖所示,下面一個步驟一個步驟的分析:

  1. 插入1,判斷無根節點,直接將1封裝爲節點並設置爲根節點
  2. 插入2,這時由於1節點沒有孩子而且只有1節點,因此直接將2加入節點1中
  3. 插入3,和2節點同樣,將3節點放入根節點中,這時根節點有3個元素了,就須要變化爲步驟4的樣子。能夠理解成將1,2,3的中間元素提取成根節點,也就是將2提出來,1做爲左孩子,3爲右孩子
  4. 插入4,4比2大,增長到節點3
  5. 插入5,5比2大,增長到節點3,4中,這時節點3,4變爲節點3,4,5,將節點3,4,5按照第三步將中間元素提取爲雙親結點,而4提取出來的4回去找雙親節點2,2節點只有一個元素,因此4加入到2節點中
  6. 插入6,6大於根節點的2和4,進入最右邊,5沒有孩子只有一個元素,加入6到5節點
  7. 插入7,這時5,6,7將6提取出放入6的雙親結點,6的雙親節點(根節點)變爲2,4,6。2,4,6提取出4變成最後的樣子

總的來講,插入方法和二分搜索樹類似。可是每一個節點能夠有1-2個元素,當節點元素個數爲3時,就會分紅3個節點並向上合併,直到合併完成。

查找

2-3樹的查找和二分搜索樹相似,不過由於一個節點可能有2個元素,須要對這兩個元素進行比較,分別前往這個節點的左,中,右孩子繼續進行比較

刪除

2-3樹的刪除稍微複雜一點,刪除可分爲兩大狀況,就是刪除葉子節點和非葉子節點。

這裏只說理論狀況,不結合代碼實現,實際上代碼實現會變得複雜也只是由於考慮的東西更多,代碼實現會變得複雜。

刪除葉子節點
  • 當前節點是3節點:直接刪除
  • 當前節點是2節點:刪除並判斷
    • 雙親是2節點,判斷兄弟節點
      • 兄弟節點是3節點,將兄弟節點移到雙親節點,再將雙親節點的另外一個元素移動到當前節點
      • 兄弟節點是2節點,先經過移動兄弟節點的中序遍歷直接後驅到兄弟節點,以使兄弟節點變爲3節點,再進行刪除
    • 雙親是3節點,拆分雙親節點使其成爲2節點,再將再將雙親節點中最接近的一個拆分key與中孩子合併,將合併後的節點做爲當前節點
  • 若2-3樹是棵滿二叉樹,刪除節點,將2-3樹層樹減小,並將兄弟節點合併到雙親節點中,同時將雙親節點的全部兄弟節點合併到雙親節點的雙親節點中,若是變爲4節點,就作分解操做
刪除非葉子節點

使用中序遍歷下的直接後繼節點key來覆蓋當前節點key,再刪除用來覆蓋的後繼節點key

瞭解完2-3樹以後咱們能夠很輕鬆的瞭解和實現紅黑樹和B-樹

紅黑樹

在開始紅黑樹以前,咱們要知道紅黑樹並不是只有2-3樹這一種實現方式,雖然2-3樹實現紅黑樹比較方便。事實是隻要知足5個定義的樹都是紅黑樹,如下是紅黑樹的定義:

  • 每一個節點是紅色或者黑色
  • 根節點是黑色
  • 每個葉子節點是黑色
  • 若是一個節點是紅色,它的孩子節點就爲黑色
  • 從任意一個節點到葉子節點,通過的黑色節點是同樣的

紅黑樹的每一個節點有兩種顏色,紅色和黑色,以下面兩圖,就是兩種不一樣實現的紅黑樹,咱們能夠看到當最後葉子節點都會增長NIL讓葉子節點統一爲黑色節點,圖二隻是沒有加上最後的黑色葉子節點

image

image

實際上,以上兩個圖中第二個圖就是用2-3樹實現的紅黑樹

2-3樹和紅黑樹的關係

image

如圖,咱們能夠看到,能夠將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節點的左元素變爲新的節點,這個節點爲紅色而且左傾。

紅黑樹要對一個插入操做進行維護,會進行左旋轉右旋轉顏色翻轉,以下圖

image

由於咱們默認新添加一個節點的時候是紅色,咱們要使節點知足上述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追求平衡,他不像AVL要求每一個節點的平衡因子絕對值必須小於等於1。這樣相對於AVL來講紅黑樹的旋轉操做會更少,例如刪除,插入節點操做,AVL是要從刪除,增長節點到根節點的全部節點進行平衡旋轉(O(logn)),而紅黑樹最多隻須要3次就能夠實現平衡O(1)(雖然經過上文實現的紅黑樹並不能作到,但有實現是能夠的),因此紅黑樹更適合增刪多的場景。

因此,在增刪多的場景適合紅黑樹,查找多的場景適合AVL。

B樹

B樹和2-3樹的原理相同,B樹也能夠是2-4樹,2-M樹 關於B樹有以下的定義,若是一棵B樹有M階(層):

  • 根節點至少有兩個孩子節點
  • 每一個節點有M-1個關鍵字key(節點中的每個元素叫關鍵字),而且以升序排列
  • 除去葉子節點和根節點其它節點至少有M/2個孩子節點

以下圖是一個3階B樹:

image

B樹的操做

對應的這個3階樹就能夠當作2-3樹,操做也是類似的

B樹有什麼用?

B樹大多用在磁盤上用於查找磁盤的地址。由於磁盤會有大量的數據,有可能沒有辦法一次將須要的全部數據加入到內存中,因此只能逐一加載磁盤頁,每一個磁盤頁就對應一個節點,而對於B樹來講,B樹很好的將樹的高度下降了,這樣就會減小IO查詢次數,雖然一次加載到內存的數據變多了,但速度絕對快於AVL或是紅黑樹的。

B+樹

B+樹和B樹相似,但多了幾條規則

  • 非葉子結點的子樹指針個數與關鍵字(節點中的元素個數)個數相同
  • 非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間)
  • 全部葉子結點有一個鏈指針
  • 全部關鍵字都在葉子結點出現
  • 只有葉子節點有Data域

如圖

image

能夠看到最主要的區別就是在葉子節點,葉子節點經過一個sqt將全部葉子節點鏈成一個鏈表,而且只有葉子節點有data域(data域就是索引指向的磁盤地址)。

B+樹的操做

B+樹和B樹的操做的優點在於B+樹的查找效率上,因此下面具體看查詢

  • B樹中每一個節點的關鍵字都有data域,而B+樹除了葉子節點,其餘節點只有索引,也就是說一樣的磁盤頁B+樹能夠容納更多的元素。
  • B+樹的範圍查詢更加方便,能夠先找到範圍下限,而後經過葉子結點的鏈表順序遍歷,直至找到上限便可。而B樹只能先找到下限,經過中序遍歷查找,直到找到上限。

至於其餘操做就和2-3樹類似

mysql的Innodb引擎採用B+樹的索引方式

總結了這些樹形結構,再對mysql中innodb存儲引擎爲何使用B+樹做爲索引作個總結

爲何不用AVL或紅黑樹?

咱們假設B+樹一個節點能夠有100個關鍵字,那麼3層的B樹能夠容納大概1000000多個關鍵字(100+101*100+101*101*100)。而紅黑樹要存儲這麼多至少要20層。因此使用B樹相對於紅黑樹和AVL能夠減小IO操做

爲何不用數組或鏈表?

鏈表查詢速度慢。數組要開闢連續空間不可能。

爲何不用哈希表?

Innodb可以進行範圍查詢,哈希表沒法實現。例如LIKE很難用哈希表實現

爲何不用B樹?

B+樹只有葉子節點存放數據,而其餘節點只存放索引,而B樹每一個節點都有Data域。因此相同大小的節點B+樹包含的索引比B樹的索引更多(由於B樹每一個節點還有Data域)

還有就是B+樹的葉子節點是經過鏈表鏈接的,因此找到下限後能很快進行區間查詢,比B樹中序遍歷快

綜上所述,mysql的Innodb引擎採用的是B+樹的索引方式

最後是以前每種數據結構的實現 github.com/PatrickLee6…

相關文章
相關標籤/搜索