數據結構—多路查找樹中的2-3樹、2-3-4樹、B樹、B+樹的原理詳解

本文詳細介紹了多路查找樹中的2-3樹、2-3-4樹、B樹、B+樹的概念的區別,以及它們的應用場景。

1 多路查找樹的概述

1.1 索引概述

  一般來說,我們操作的數據都是存儲在內存(CPU)中的,但如若我們要操作的數據集非常大,大到內存已經沒辦法處理了怎麼辦呢?如數據庫中的上千萬條記錄的數據表、硬盤中的上萬個文件等,他們必然不能都存儲在內存中,而是存儲在外存中的。
  對於外存中的數據,常見如數據庫,我們通常通過索引表來進行數據查找和交互,一般來說,索引表本身也很大,因爲數據庫的數據非常多,因此索引也不可能全部存儲在內存中,因此索引表往往也是以索引文件的形式存儲的磁盤上。Mysql的MyISAM引擎的索引文件和數據文件是分離的,一張數據庫表就有它對應的索引表,索引表中一個索引對應一條數據庫記錄的磁盤地址,內存中發起請求通過指定索引的查找索引表即可定位唯一的一條數據;當然Mysql的InnoDB引擎的索引表也是數據表,即索引表保存了索引和完整的數據記錄。但是不管怎麼說數據的查找都會依賴索引,並且建議通過索引查找,因爲如果沒有走索引,那就會走全表掃描,使得查找效率大大降低。
  因爲索引文件同樣存儲在磁盤上,這樣的話,索引查找過程中每查找一次就要產生一次磁盤I/O消耗,相對於CPU存取,I/O存取的消耗要高几個數量級,訪問磁盤的成本大概是訪問內存的十萬倍左右。關於cpu和磁盤速度對比:鏈接
  實際上,考慮到磁盤IO是非常高昂的操作,計算機操作已經系統做了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,局部預讀性原理告訴我們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。
  每一次IO讀取的數據我們稱之爲一頁(page)。具體一頁有多大數據跟操作系統有關,一般爲4k或8k大小的連續磁盤塊,也就是我們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計非常有幫助,通常,索引節點的大小被設計爲一頁的大小。

1.2 多路查找樹的引出

  對於一旦涉及到這樣的外部存儲設備(外存),關於時間複雜度的計算就會發生變化,訪問某個表/集合元素的時間已經不僅僅是尋找該元素所需比較次數的函數,我們必須考慮對硬盤IO進行操作的時間。由於IO耗時遠大於CPU耗時,所以此處評價一個數據結構作爲索引的優劣最重要的指標就是要儘量減少查找過程中磁盤IO的存取次數
  我們之前談的樹,都是一個節點可以有多個孩子,但是它自身只存儲一個元素。二叉樹限制更多,節點最多隻能有兩個孩子。一個節點只能存儲一個元素,在元素非常多的時候,就使得要麼樹的度非常大(節點擁有子樹的個數的最大值),要麼樹的高度非常大,甚至兩者都必須足夠大才行,這就使得IO次數非常多,這顯然成了時間效率上的瓶頸,並且由於一次IO讀取一個節點的數據,普通二叉樹並不能容納更多的數據,這樣又造成了磁盤塊空間的浪費。
  以上種種限制迫使我們設計出每一個節點可以存儲多個元素,並且數據結構的高度可控的數據結構,爲此引入了多路查找樹的概念。一顆平衡多路查找樹同樣可以使得數據的查找效率保證在O(logN)這樣的對數級別上,此時底數爲叉數或者階。
  多路查找樹(muitl-way search tree),其每一個節點的孩子數可以多於兩個,且每一個節點處可以存儲多個元素。由於它是一顆平衡查找樹,所有元素之間存在某種特定的排序關係。在這裏,每一個節點可以存儲多少個元素,以及它的孩子數的多少是非常關鍵的。爲此,我們講解它的4種特殊形式:2-3樹、2-3-4樹、B樹和B+樹。

2 2-3樹

  2-3樹是一種比較早期的自平衡多路查找樹,構建跟維繫一棵2-3樹,比平衡二叉樹要複雜的多。因此學習2-3樹以及其他多路查找樹還需要平衡二叉樹的知識:平衡二叉樹(AVL樹)的詳解以及Java代碼的完全實現
  2-3樹中的一個節點可能具有兩個孩子,我們稱它爲2-節點;或三個孩子,我們稱它爲3-節點。後面會講道實際上它就是一顆B樹。它的節點具有如下性質:

  1. 一個2節點包含一個元素和兩個孩子(或沒有孩子),且與二叉排序樹類似,左子樹包含的元素小於該元素,右子樹包含的元素大於該元素。不過,與二叉排序樹不同的是,這個2節點要麼沒有孩子,要有就有兩個,不能只有一個孩子。
  2. 一個3節點包含一小一大兩個元素和三個孩子(或沒有孩子),一個3節點要麼沒有孩子,要麼具有3個孩子。如果某個3節點有孩子的話,左子樹包含小於較小元素的元素,右子樹包含大於較大元素的元素,中間子樹包含介於兩元素之間的元素。
  3. 2-3樹中所有的葉子都在同一層次上,因此是一顆平衡排序樹。其時間複雜度和AVL樹一樣都是O(logN)。

如下圖所示,就是一棵2-3樹:
在這裏插入圖片描述

2.1 2-3樹查找的實現

  跟二叉排序樹的查找類似:從根節點開始比較,若相等,則結束。如果小於根節點,則說明它應該在左邊,選定左節點進行比較;如果大於根節點,則說明在右邊,選定右節點進行比較,如不相等,則繼續循環。如到最後訪問到空節點,則說明沒找到。
  只不過對於3節點的情況,就需要判斷左中右子樹,原理一樣,在此不再贅述。

2.2 2-3樹插入的實現

  對於2-3樹的插入來說,與平衡二叉樹相同,插入操作一定是發生在葉子節點上,並且節點的插入和刪除都有可能導致不平衡的情況發生,在插入和刪除節點時也是需要動態維持平衡的,但維持平衡的策略和AVL樹是不一樣的。
  AVL樹向下添加節點之後通過旋轉來恢復平衡,而2-3樹是通過節點向上分裂來維持平衡的,也就是說2-3樹插入元素的過程中層級是向上增加的,因此不會導致葉子節點不在同一層級的現象發生,也就不需要旋轉了。2-3樹插入可分爲三種情況:

  1. 對於空樹,插入一個2-節點,讓其成爲根節點即可,這很容易理解。

  2. 插入節點到一個2-節點的葉子上。
    由於其本身就只有一個元素,所以只需要將其升級爲3-節點即可。
    如下圖所示。我們希望從左邊的2-3樹中插入元素3,根據遍歷可知,3比8小、比4小,於是就只能考慮插入到葉子節點1所在的位置,因此很自然的想法就是將此節點變成一個3-節點,即右圖這樣完成插入操作。當然,要視插入的元素與當前葉子節點的元素比較大小後,決定誰在左誰在右。例如,若插入的是0,則此節點就是“0”在左“1”在右了。
    在這裏插入圖片描述

  3. 要往3-節點中插入一個新元素。因爲3-節點本身已經是2-3樹的節點最大容量(已經有兩個元素),因此就需要將其向上分裂。
    a) 向一顆只含有一個3-節點的樹中插入元素;
      先臨時將新元素存入該節點中,使之成爲一個4節點。然後再進行向上分裂,節點的三個元素中,位於中間的元素成爲根節點,左右兩個元素分別成爲一個2節點作爲左右子樹。
    在這裏插入圖片描述

b) 向一個父節點爲2-節點的3-節點中插入元素。
  構造一個臨時的4-節點並將其向上分裂,但此時我們不會爲中鍵創建一個新節點,而是將其移動至原來的父節點中。
  如下圖,需要向左圖中插入元素5。經過遍歷可得到元素5比8小比4大,因此它應該是需要插入在擁有6、7元素的3節點位置。問題就在於,6和7節點已經是3節點,不能再加。此時發現它的雙親節點4是個2節點,因此考慮讓它升級爲3節點,這樣它就得有三個孩子,於是就想到,將6、7節點拆分,讓6與4結成3節點,將5成爲它的中間孩子,將7成爲它的右孩子,如下圖的右圖所示。
在這裏插入圖片描述

c) 向一個父節點爲3-節點的3-節點中插入元素。
  同樣先構造一個臨時的4-節點並將其向上分裂,由於此時父節點已經是3-節點,因此父節點也變成了4-節點,需要繼續向上分裂,直到某個父節點不是4-節點爲止,如果到了根節點還是4-節點,那麼根節點繼續向上分裂,此時樹的層級會增加一層,當然不利於查找。
在這裏插入圖片描述

  如上圖所示,需要向圖中插入元素7。經過遍歷可得到元素7應該被插入在擁有5、6元素的3-節點位置,此時先臨時插入7,使其成爲4-節點。
在這裏插入圖片描述

  然後向上分裂,此時父節點也成爲4-節點:
在這裏插入圖片描述

  此時父節點只能繼續向上分裂,直到根節點——因爲它是2-節點,因此會變成3-節點,並沒有超出2-3樹的定義,分裂結束。
在這裏插入圖片描述

  如果此時再插入18,那麼會如上面一樣就行向上分裂,但此時根節點已經是3-節點了,在合併一個節點會成爲4-節點,此時根節點會向上分裂,增加一層,形成下圖4的結構:
在這裏插入圖片描述

  以上就是2-3樹插入節點同時動態維持平衡的方法,自始至終葉子節點均在同一層級上。只有當根節點是4-節點時,拆分後樹的高度才加一!

2.3 2-3樹刪除的實現

  2-3樹的刪除也分爲三種情況,與插入相反。以下圖的2-3樹爲例:
在這裏插入圖片描述

  1. 當刪除元素位於3-節點的葉子節點上
      只需要在該節點處刪除該元素即可,不會影響到整棵樹的其他節點結構。
      例如,想要刪除5,那麼直接刪除即可:
    在這裏插入圖片描述

  2. 當刪除元素位於非葉子節點
      使用中序遍歷找到待刪除節點的後繼節點,然後將後繼節點與待刪除節點位置互換,此時就將問題轉化爲刪除節點爲葉子節點(平衡樹的非葉子節點中序遍歷後繼節點肯定是葉子節點),如果該葉子是3-節點,則跟情況(1)一樣,如果該節點是2-節點,則跟後面的情況(3)一樣;

  3. 當刪除元素位於2-節點的葉子節點上
      如果按照以前樹的理解,刪除即可,可現在的2-3樹的定義告訴我們這樣做是不可以的。可能會導致不平衡的情況發生。比如下圖所示,如果我們刪除了節點0,那麼父節點本來是一個3-節點(它必須擁有3個孩子),此時它就不滿足定義了。
      因此,對於刪除葉子節點是2節點的情況,我們需要分多種情形來處理。總結起來就是當前待刪除2-葉子節點,兄弟節點與父節點,分別是2-節點還是3-節點的情況,即:
    a) 父節點爲2-節點,且擁有一個3-節點的兄弟節點。
      將父節點移動到當前位置,再將兄弟節點中最接近當前位置的key移動到雙親節點中
    b) 父節點爲3-節點。 即此時有兩個兄弟節點,而兄弟節點又可能有多種情況,窮舉起來有:刪除節點的  位置左中右3個,以及另外兩個兄弟節點是否爲2-節點或3-節點的4種情況,總共3*4=12種。即:
      i. 若刪除的是左或右節點,且中間節點爲2-節點,則此時父節點的左或右key下移,與中間節點合併,此時父節點爲2-節點,兩個子節點,樹滿足2-3樹,完成調整;
      ii. 若刪除的是左或右節點,且中間節點爲3-節點,則此時父節點的一個key下移,中間節點的一個key上移與父節點合併,此時父節點爲3-節點,3個子節點,樹滿足2-3樹,完成調整;
      iii. 若刪除的是中間節點,且右節點爲2-節點,則此時父節點的一個key下移,與右節點合併,此時父節點爲爲2-節點,兩個子節點,樹滿足2-3樹,完成調整;
      iv. 若刪除的是中間節點,且右節點爲3-節點,則此時父節點的一個key下移,右節點的一個key上移與父節點合併,此時父節點爲3-節點,3個子節點,樹滿足2-3樹,完成調整。
      i與ii刪除左或右節點兩種情況,中間節點2-節點或3-節點兩種情況,兄弟節點2-節點或3-節點兩種情況,總共 2x2x2=8 種;刪除中間節點一種情況,iii與iv右節點2-節點或3-節點兩種情況,左節點2-節點或3-節點兩種情況,總共 1x2x2=4 種; 4+8=12 種全齊,雖然場景有12種,但是處理的方式只有2種,一種是父節點下移與子節點合併,另一種是父節點下移成單獨一個子節點,然後3-節點的子節點上移一個key與父節點合併。
    c) 父節點爲2-節點,且擁有一個2-節點的兄弟節點。
      先通過移動兄弟節點的中序遍歷直接後繼到兄弟節點,以使兄弟節點變爲3節點;再對該節點進行刪除操作。
      如下案例,需要刪除5進行的一系列操作。
    在這裏插入圖片描述
    在這裏插入圖片描述
      在移動節點11到10的位置上時,實際上就是發生了2-3樹節點刪除的情況2。上圖是調整後的結構。
    在這裏插入圖片描述

3 2-3-4樹

  2-3-4樹只是在2-3樹的基礎上進行了擴展。2-3-4樹也是一棵自平衡的多路查找樹,具有如下性質:

  1. 任一節點只能是1個或2個或3個key,對應的子節點爲2個子節點或3個子節點或4個子節點;
  2. 所有葉子節點到根節點的長度一致;
  3. 每個節點的key從左到右保持了從小到大的順序,兩個key之間的子樹中所有的key一定大於它的父節點的左key,小於父節點的右key,對於3個key的節點,兩兩key之間也是如此。

  下圖就是一顆典型的2-3-4樹:
在這裏插入圖片描述

3.1 2-3-4樹的插入操作

  2-3-4樹插入節點跟刪除節點的處理,實際上跟2-3樹很像,特別是插入節點,基本上跟2-3樹是一模一樣,只是分裂的條件由3-節點變成了4-節點而已,即,

  1. 如果待插入的節點不是4-節點,則直接插入即可;
  2. 如果待插入的節點是4-節點,則先把新節點臨時插入進去變成5-節點,然後對5節點進行向上分裂、合併,5-節點分裂成兩個2-節點(5-節點最小的元素、5-節點第二個元素)、1個3-節點(5-節點後兩個元素),然後將分裂之後的第2個2-節點向上合併到父節點中,然後把父節點作爲插入元素之後的當前節點,重複(1)、(2)步驟,直到滿足2-3-4樹的定義性質。

  例如,需要對上圖的2-3-4樹插入23:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
  2-3-4樹節點的插入也比較簡單的,其實從前面到這裏可以看出一些規律,就是不管是二叉排序樹也好,還是平衡二叉樹,以及2-3樹的節點插入,相對來說都算簡單,但是對於一棵樹節點的刪除卻比較複雜,有的甚至需要不斷的回溯到根節點才能把樹調整平衡。
  所以,關於2-3-4樹節點的刪除也不簡單,至少比節點的插入要複雜麻煩的多。

3.2 2-3-4樹的刪除操作

  1. 當刪除的節點是非2-節點的葉子節點,則將要刪除的目標key刪除即可;
  2. 當刪除的節點是非葉子節點,先使用中序遍歷找到待刪除節點的後繼節點,然後將後繼節點與待刪除節點位置互換,此時就將問題轉化爲刪除節點爲葉子節點(平衡樹的非葉子節點中序遍歷後繼節點肯定葉子節點),如果該葉子是非2-節點,則跟情況(1)一樣,如果該節點是2-節點,則跟後面的情況(3)一樣;
  3. 當刪除的節點是2-節點的葉子節點,則將節點刪除,此時樹肯定需要調整,即:
    a) 當父節點是2-節點,兄弟節點是非2-節點
      將兄弟節點的一個key上移成父節點,而父節點下移成子節點,此時樹滿足2-3-4樹,完成調整。
    b) 當父節點是2-節點,兄弟節點也是2-節點;
      先通過移動兄弟節點的中序遍歷直接後繼到兄弟節點,以使兄弟節點變爲3-節點;再對該節點進行刪除操作。
    c) 當父節點是非2-節點,即此時有兩個或三個兄弟節點,此時看相鄰兄弟節點是否“豐滿”,也即是否爲4-節點,如下:
    i. i.若刪除節點的相鄰兄弟節點爲非4-節點,則父節點的一個key下移,與相鄰兄弟節點合併,此時樹滿足2-3樹,完成調整;
    ii. ii.若刪除節點的相鄰兄弟節點爲4-節點,則父節點的一個key下移成1個key的節點,相鄰兄弟節點的一個key上移與父節點合併,此時樹滿足2-3樹,完成調整;

  可以看出來,2-3-4樹的刪除操作雖然比較複雜,但是和2-3樹的刪除操作非常相似,我們理解其中一種樹的操作,那麼理解另外一種樹的刪除操作就是非常簡單的一件事了。

3.3 總結

  對於2-3樹和2-3-4樹的刪除與插入,都是以保持葉子節點的深度相同爲基礎的。自始至終葉子節點均在同一層級上。
  插入的時候,即標準的二叉樹的的高度是由上到下的增加的,而2-3樹和2-3-4樹的高度生長是由下至上的。刪除的時候不斷的用上邊的節點來彌補下邊的節點,保持地基的穩定,再縮減高度。
  2-3樹、2-3-4樹和平衡二叉樹的查找時間複雜度都是O(logN),但是2-3樹和2-3-4樹的節點能夠保存更多的數據,並且擁有更低的深度,在常被用來構建需要內、外存交互的數據結構,能夠有效的降低IO次數,提升查找效率。但是最常見的數據庫索引表卻不是基於2-3樹或者2-3-4樹來實現的,因爲後來又有了更加高效的平衡多路查找樹結構。
  理解2-3-4樹還是理解紅黑樹的基礎,通過簡單的規則它們可以互相轉換!

4 B樹

  B樹(Balance Tree)由Rudolf Bayer, Edward M. McCreight於1970發表的一篇論文《Organization and Maintenance of Large Ordered Indices》中首次提出。
  B樹(B-tree)是一種自平衡的多路查找樹,也稱B-樹,節點最大的孩子數目稱爲B樹的階(order)。2-3樹和2-3-4樹都是B樹的特例,因此,2-3樹是3階B樹,2-3-4樹是4階B樹。
  一個m階的B樹具有如下屬性:

  1. 它是一顆平衡查找樹,所有節點關鍵字是按遞增次序排列,並遵循左小右大原則;
  2. 根節點或者是葉節點,或者最少有2個子節點,最多有m個子節點;
  3. 非根節點或者是葉節點,或者最少有ceil(m/2)個子節點,最多有m個子節點;
  4. 根節點的關鍵字數量大於等於1,小於等於m-1;(注:ceil()是個朝正無窮方向取整的函數 如ceil(1.1)結果爲2);
  5. 非根節點的關鍵字數量大於等於ceil(m/2)-1個且小於等於m-1個
  6. 一個k-節點(2<=k<=m),要麼有k個子節點,要麼沒有子節點;
  7. 所有葉子節點都位於同一層,或者說根節點到每個葉子節點的長度都相同。

  和平衡二叉樹一樣,B樹也能以O(logn)的時間複雜度運行進行查找、順序讀取、插入和刪除的數據結構。
  不同的是,平衡二叉樹是一般是用於優化內存中的數據的查找速度的數據結構,B樹一般用於優化外存中數據的查找數據的數據結構,普遍運用在數據庫索引結構和文件系統。
  對某個節點的訪問就會發起一次磁盤IO請求,IO操作耗時遠大於內存中操作的耗時。如果一棵B樹的階爲1001(即1個節點包含1000個關鍵字),高度爲2,它可以儲存超過10億個關鍵字。那麼在這棵樹上,尋找某一個關鍵字至多需要兩次硬盤的讀取即可。如果採用二叉樹來存儲這10億個關鍵字,那麼會需要非常多次數的IO請求。
  由於B-tree每個節點能包含比二叉樹更多的數據,同時樹的層級比原來的二叉樹更少的特性,加上數據庫充分利用了磁盤塊的原理(磁盤數據存儲是採用塊的形式存儲的,每個塊的大小爲4k或8k,每次IO進行數據讀取時,同一個磁盤塊的數據可以一次性讀取出來),因此採用B樹作爲索引結構,能夠減少定位記錄時所經歷IO次數,從而加快存取速度。可以說,B樹的數據結構就是爲內外存的數據交互準備的。

4.1 B樹查找算法分析

  在B-樹中進行查找包含兩種基本操作:

  1. 在B樹中查找節點;
  2. 在節點中查找關鍵字。

  由於B樹通常存儲在磁盤上, 則前一查找操作是在磁盤上進行的, 而後一查找操作是在內存中進行的, 即在磁盤上找到指針p 所指節點後,先將節點中的信息讀入內存, 然後再利用順序查找或折半查找查詢等於K 的關鍵字。顯然,在磁盤上進行一次查找比在內存中進行一次查找的時間消耗多得多。
因此,在磁盤上進行查找的次數、即待查找關鍵字所在節點在B樹上的層次樹,是決定B樹查找效率的首要因素。
  對於n個關鍵字的m階B樹,最壞情況的查找次數如下:

  1. 第一層爲根,至少一個節點,根至少有兩個孩子,因此在第二層至少有兩個節點。
  2. 除根和樹葉外,其它節點至少有ceil(m/2)個孩子,因此第三層至少有2* ceil(m/2)個節點,在第四層至少有2* ceil(m/2)^2個節點……
  3. 那麼在第k+1層至少有2* ceil(m/2)^(k-1)個節點,而J+1層的節點爲葉子節點,於是葉子節點的個數n+1。
  4. 若m階B樹有n個關鍵字,那麼當你找到了葉子節點,其實也就等於查找不成功的節點爲n+1,因此n+1≥2* ceil(m/2)^(k-1),即:
    在這裏插入圖片描述
      也就是說,在含有n個關鍵字的m階B樹上查找時,從根節點到關鍵字節點的路徑上涉及的節點數不超過log((n+1)/2)+1,底數爲ceil(m/2)。

4.2 B樹的插入和刪除

  B樹的插入和刪除和2-3樹和2-3-4樹基本一致。只不過把具體的階數採用字母代替。
插入:
  對高度爲h的m階B樹,新節點一般插第h層。通過檢索可以確定關鍵碼應插入的位置,

  1. 若該節點中關鍵碼個數小於等於m-1,則直接插入就可
  2. 若該節點中關鍵碼個數等於m-1,則將引起節點的分裂,以中間的關鍵碼爲界將節點一分爲二(如果是偶數則取中偏小的關鍵碼),產生了一個新的節點,並將中間關鍵碼插入到父節點中;然後將父節點看作當前插入元素之後的節點,循環判斷(1),(2)即可。

刪除:
  B樹刪除:首先要查找該值是否在B樹中存在,如果存在,判斷該元素是否存在左右孩子節點,如果有,則上移孩子節點中的相近節點(左孩子最右邊的節點或者右孩子最左邊的節點)到父節點中,然後根據移動之後的情況;如果沒有,進行直接刪除;如果不存在對應的值,則刪除失敗。

  1. 如果當前要刪除的值位於非葉子節點,則用後繼值覆蓋要刪除的值,再用後繼值所在的分支刪除該後繼值。(該後繼值必須位於葉子節點上)
  2. 該節點值個數不小於Math.ceil(m/2)-1(取上限函數),結束刪除操作,否則下一步
  3. 如果兄弟節點值個數大於Math.ceil(m/2)-1,則父節點中下移到該節點,兄弟的一個值上移,刪除操作結束。

  將父節點的key下移與當前的節點和他的兄弟姐妹節點key合併,形成一個新的節點,有些節點可能有左兄弟,也有右兄弟,我們可以任意選擇一個兄弟節點即可。

5 B+樹

  B+樹可以說是B樹的升級版,相對於B樹來說B+樹更充分的利用了節點的空間,讓查詢速度更加穩定,其速度完全接近於二分法查找。大部分文件系統和數據均採用B+樹來實現索引結構。
  一棵m階的B+樹和m階的B樹的差異在於:

  1. 有n棵子樹的非葉節點中包含有n個關鍵字(B樹中是n-1個關鍵字),但是每個關鍵字不保存數據,只用來保存葉子節點相同關鍵字的索引,所有數據都保存在葉子節點。(此處,對於非葉節點的m顆子樹和n個關鍵字的關係,mysql的索引結構似乎是m=n+1,而不是上面的m=n)
  2. 所有的非葉節點元素都同時存在於子節點,在子節點元素中是最大(或最小)元素。
  3. 所有的葉子節點包含全部關鍵字的信息,及指向含這些關鍵字所指向的具體磁盤記錄的指針data,並且每一個葉子節點帶有指向下一個相鄰的葉節點指針,形成鏈表;

  如下,是一顆典型的B樹:
在這裏插入圖片描述
  如下,是一顆普通定義的典型的B+樹:
在這裏插入圖片描述
  如下,是一顆mysql索引表中實現的一種B+樹:
在這裏插入圖片描述
  由上可知,B+樹的具體實現是多種多樣的的,我們以該記住B樹和B+樹的主要區別是:B+樹的非葉子節點不存儲數據,並且葉子節點會形成鏈表。鏈表也可以具體選擇實現,甚至可以是雙向鏈表或者環形鏈表。
  B+樹的插入、刪除過程也都與B樹類似,只不過插入和刪除的元素都是在葉子節點上進行而已,因此可能需要調整非葉節點的索引key。

5.1 總結

  1. B樹的非葉節點會存儲關鍵字及其對應的數據地址,B+樹的非葉節點只存關鍵字索引,不會存具體的數據地址,因此B+樹的一個節點相比B樹能存儲更多的索引元素,一次性讀入內存的需要查找的關鍵字也就越多,B+樹的高度更小,相對IO讀寫次數就降低了。
  2. B樹的查詢效率並不穩定,最好的情況只查詢一次(根節點),最壞情況是查找到葉子節點,而B+樹由於非葉節點不存數據地址,而只是葉子節點中關鍵字的索引。所有查詢都要查找到葉子節點纔算命中,查詢效率比較穩定。這對於sql語句的優化是非常有幫助的。
  3. B+樹所有葉子節點形成有序鏈表,只需要去遍歷葉子節點就可以實現整棵樹的遍歷。方便數據的範圍查詢,而B樹不支持這樣的操作或者說效率太低。
  4. 現代數據庫和文件系統的索引表大部分是使用B+樹來實現的,但並不是全部。

如果有什麼不懂或者需要交流,可以留言。另外希望點贊、收藏、關注,我將不間斷更新各種Java學習博客!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章