1. 程式人生 > >B樹(B-Tree)與B+樹詳解

B樹(B-Tree)與B+樹詳解

注意:首先需要說明的一點是:B-樹就是B樹,沒有所謂的B減樹

引言

  我們都知道二叉查詢樹的查詢的時間複雜度是O(log N),其查詢效率已經足夠高了,那為什麼還有B樹和B+樹的出現呢?難道它兩的時間複雜度比二叉查詢樹還小嗎? 
  答案當然不是,B樹和B+樹的出現是因為另外一個問題,那就是磁碟IO;眾所周知,IO操作的效率很低,那麼,當在大量資料儲存中,查詢時我們不能一下子將所有資料載入到記憶體中,只能逐一載入磁碟頁,每個磁碟頁對應樹的節點。造成大量磁碟IO操作(最壞情況下為樹的高度)。平衡二叉樹由於樹深度過大而造成磁碟IO讀寫過於頻繁,進而導致效率低下。 
  所以,我們為了減少磁碟IO的次數,就你必須降低樹的深度,將“瘦高”的樹變得“矮胖”。一個基本的想法就是: 
  (1)、每個節點儲存多個元素  
  (2)、摒棄二叉樹結構,採用多叉樹

  這樣就引出來了一個新的查詢樹結構 ——多路查詢樹。 根據AVL給我們的啟發,一顆平衡多路查詢樹(B~樹)自然可以使得資料的查詢效率保證在O(logN)這樣的對數級別上。

下面來具體介紹一下B樹(Balance Tree),

B樹

一個m階的B樹具有如下幾個特徵:B樹中所有結點的孩子結點最大值稱為B樹的階,通常用m表示。一個結點有k個孩子時,必有k-1個關鍵字才能將子樹中所有關鍵字劃分為k個子集。

1.根結點至少有兩個子女。
2.每個中間節點都包含k-1個元素和k個孩子,其中 ceil(m/2) ≤ k ≤ m
3.每一個葉子節點都包含k-1個元素,其中 ceil(m/2) ≤ k ≤ m
4.所有的葉子結點都位於同一層。
5.每個節點中的元素從小到大排列,節點當中k-1個元素正好是k個孩子包含的元素的值域劃分
6.每個結點的結構為:(n,A0,K1,A1,K2,A2,…  ,Kn,An)
    其中,Ki(1≤i≤n)為關鍵字,且Ki<Ki+1(1≤i≤n-1)。
Ai(0≤i≤n)為指向子樹根結點的指標。且Ai所指子樹所有結點中的關鍵字均小於Ki+1。
n為結點中關鍵字的個數,滿足ceil(m/2)-1≤n≤m-1。
這裡寫圖片描述

查詢

  以上圖為例:若查詢的數值為5: 
  第一次磁碟IO:在記憶體中定位(與17、35比較),比17小,左子樹; 
  第二次磁碟IO:在記憶體中定位(與8、12比較),比8小,左子樹; 
  第三次磁碟IO:在記憶體中定位(與3、5比較),找到5,終止。 
整個過程中,我們可以看出:比較的次數並不比二叉查詢樹少,尤其適當某一節點中的資料很多時,但是磁碟IO的次數卻是大大減少。比較是在記憶體中進行的,相比於磁碟IO的速度,比較的耗時幾乎可以忽略。所以當樹的高度足夠低的話,就可以極大的提高效率。相比之下,節點中的元素多點也沒關係,僅僅是多了幾次記憶體互動而已,只要不超過磁碟頁的大小即可。

插入

  對高度為k的m階B樹,新結點一般是插在葉子層。通過檢索可以確定關鍵碼應插入的結點位置。然後分兩種情況討論: 
  1、  若該結點中關鍵碼個數小於m-1,則直接插入即可。 
  2、  若該結點中關鍵碼個數等於m-1,則將引起結點的分裂。以中間關鍵碼為界將結點一分為二,產生一個新結點,並把中間關鍵碼插入到父結點(k-1層)中 
  重複上述工作,最壞情況一直分裂到根結點,建立一個新的根結點,整個B樹增加一層。

例如:在下面的B樹中插入key:4

這裡寫圖片描述

第一步:檢索key插入的節點位置如上圖所示,在3,5之間;

第二步:判斷節點中的關鍵碼個數: 
  節點3,5已經是兩元素節點,無法再增加。父親節點 2, 6 也是兩元素節點,也無法再增加。根節點9是單元素節點,可以升級為兩元素節點。;

第三步:結點分裂: 
  拆分節點3,5與節點2,6,讓根節點9升級為兩元素節點4,9。節點6獨立為根節點的第二個孩子。

最終結果如下圖:雖然插入比較麻煩,但是這也能確保B樹是一個自平衡的樹 
這裡寫圖片描述

這裡寫圖片描述

刪除

  B樹中關鍵字的刪除比插入更復雜,在這裡,只介紹其中的一種方法: 
   
  在B樹上刪除關鍵字k的過程分兩步完成:

   (1)找出該關鍵字所在的結點。然後根據 k所在結點是否為葉子結點有不同的處理方法。
   (2)若該結點為非葉結點,且被刪關鍵字為該結點中第i個關鍵字key[i],則可從指標son[i]所指的子樹中
   找出最小關鍵字Y,代替key[i]的位置,然後在葉結點中刪去Y。

因此,把在非葉結點刪除關鍵字k的問題就變成了刪除葉子結點中的關鍵字的問題了。

在B-樹葉結點上刪除一個關鍵字的方法: 
  首先將要刪除的關鍵字 k直接從該葉子結點中刪除。然後根據不同情況分別作相應的處理,共有三種可能情況:

(1)如果被刪關鍵字所在結點的原關鍵字個數n>=ceil(m/2),說明刪去該關鍵字後該結點仍滿足B樹的定義。
這種情況最為簡單,只需從該結點中直接刪去關鍵字即可。

(2)如果被刪關鍵字所在結點的關鍵字個數n等於ceil(m/2)-1,說明刪去該關鍵字後該結點將不滿足B樹的定義,
需要調整。

調整過程為:
   如果其左右兄弟結點中有“多餘”的關鍵字,即與該結點相鄰的右(左)兄弟結點中的關鍵字數目大於
ceil(m/2)-1。則可將右(左)兄弟結點中最小(大)關鍵字上移至雙親結點。而將雙親結點中小(大)於該上
移關鍵字的關鍵字下移至被刪關鍵字所在結點中。
   如果左右兄弟結點中沒有“多餘”的關鍵字,即與該結點相鄰的右(左)兄弟結點中的關鍵字數目均等於
ceil(m/2)-1。這種情況比較複雜。需把要刪除關鍵字的結點與其左(或右)兄弟結點以及雙親結點中分割二者
的關鍵字合併成一個結點,即在刪除關鍵字後,該結點中剩餘的關鍵字加指標,加上雙親結點中的關鍵字Ki一起,
合併到Ai(是雙親結點指向該刪除關鍵字結點的左(右)兄弟結點的指標)所指的兄弟結點中去。如果因此使雙親
結點中關鍵字個數小於ceil(m/2)-1,則對此雙親結點做同樣處理。以致於可能直到對根結點做這樣的處理而使
整個樹減少一層。
12345678910111213141516

總之,設所刪關鍵字為非終端結點中的Ki,則可以指標Ai所指子樹中的最小關鍵字Y代替Ki,然後在相應結點中刪除Y。對任意關鍵字的刪除都可以轉化為對最下層關鍵字的刪除。

下面舉一個簡單的例子:刪除元素11. 
這裡寫圖片描述

第一步:判斷該元素是否在葉子結點上。 
   該元素在葉子節點上,可以直接刪去,但是刪除之後,中間節點12只有一個孩子,不符合B樹的定義:每個中間節點都包含k個孩子,(其中 ceil(m/2) <= k <= m)所以需要調整;

第二步:判斷其左右兄弟結點中有“多餘”的關鍵字,即:原關鍵字個數n>=ceil(m/2) - 1; 
  顯然結點11的右兄弟節點中有多餘的關鍵字。那麼可將右兄弟結點中最小關鍵字上移至雙親結點。而將雙親結點中小於該上移關鍵字的關鍵字下移至被刪關鍵字所在結點中即可
這裡寫圖片描述

注意

  ①、B樹主要用於檔案系統以及部分資料庫索引,例如: MongoDB。而大部分關係資料庫則使用B+樹做索引,例如:mysql資料庫; 
  ②、從查詢效率考慮一般要求B樹的階數m >= 3; 
  ③、B-樹上演算法的執行時間主要由讀、寫磁碟的次數來決定,故一次I/O操作應讀寫儘可能多的資訊。因此B-樹的結點規模一般以一個磁碟頁為單位。一個結點包含的關鍵字及其孩子個數取決於磁碟頁的大小。

這裡寫圖片描述

這裡寫圖片描述
這裡寫圖片描述

儘管在B樹中的刪除操作看起來很複雜,但是對一棵高度為h的樹,它只需要O(h) O(h)O(h)次磁碟造作,因為在遞迴呼叫該過程之間,僅需O(1) O(1)O(1)次對Disk_read和Disk_weite的呼叫,所需要的CPU時間為O(th)=O(tlog_{t }n)
 

B+ 樹

  B+樹是B樹的變種,有著比B樹更高的查詢效率。下面,我們就來看看B+樹和B樹有什麼不同

特點

一個m階的B+樹具有如下幾個特徵:

1.有k個子樹的中間節點包含有k個元素(B樹中是k-1個元素),每個元素不儲存資料,只用來索引,所有資料
都儲存在葉子節點。

2.所有的葉子結點中包含了全部元素的資訊,及指向含這些元素記錄的指標,且葉子結點本身依關鍵字的大小
自小而大順序連結。

3.所有的中間節點元素都同時存在於子節點,在子節點元素中是最大(或最小)元素。
1234567

下面是一棵3階的B+樹:
這裡寫圖片描述

B+樹通常有兩個指標,一個指向根結點,另一個指向關鍵字最小的葉子結點。因些,對於B+樹進行查詢兩種運算:一種是從最小關鍵字起順序查詢,另一種是從根結點開始,進行隨機查詢。

查詢

  B+樹的優勢在於查詢效率上,下面我們做一具體說明: 
   首先,B+樹的查詢和B樹一樣,類似於二叉查詢樹。起始於根節點,自頂向下遍歷樹,選擇其分離值在要查詢值的任意一邊的子指標。在節點內部典型的使用是二分查詢來確定這個位置。 
   (1)、不同的是,B+樹中間節點沒有衛星資料(索引元素所指向的資料記錄),只有索引,而B樹每個結點中的每個關鍵字都有衛星資料;這就意味著同樣的大小的磁碟頁可以容納更多節點元素,在相同的資料量下,B+樹更加“矮胖”,IO操作更少 
   B樹的衛星資料: 
   這裡寫圖片描述

  B+樹的衛星資料:

這裡寫圖片描述

  需要補充的是,在資料庫的聚集索引(Clustered Index)中,葉子節點直接包含衛星資料。在非聚集索引(NonClustered Index)中,葉子節點帶有指向衛星資料的指標。 
    
   (2)、其次,因為衛星資料的不同,導致查詢過程也不同;B樹的查詢只需找到匹配元素即可,最好情況下查詢到根節點,最壞情況下查詢到葉子結點,所說效能很不穩定,而B+樹每次必須查詢到葉子結點,效能穩定 
   (3)、在範圍查詢方面,B+樹的優勢更加明顯 
   B樹的範圍查詢需要不斷依賴中序遍歷。首先二分查詢到範圍下限,在不斷通過中序遍歷,知道查詢到範圍的上限即可。整個過程比較耗時。 
   而B+樹的範圍查詢則簡單了許多。首先通過二分查詢,找到範圍下限,然後同過葉子結點的連結串列順序遍歷,直至找到上限即可,整個過程簡單許多,效率也比較高。 
   例如:同樣查詢範圍[3-11],兩者的查詢過程如下: 
   B樹的查詢過程: 
這裡寫圖片描述

 B+樹的查詢過程:

這裡寫圖片描述

插入

   B+樹的插入與B樹的插入過程類似。不同的是B+樹在葉結點上進行,如果葉結點中的關鍵碼個數超過m,就必須分裂成關鍵碼數目大致相同的兩個結點,並保證上層結點中有這兩個結點的最大關鍵碼。

刪除

  B+樹中的關鍵碼在葉結點層刪除後,其在上層的複本可以保留,作為一個”分解關鍵碼”存在,如果因為刪除而造成結點中關鍵碼數小於ceil(m/2),其處理過程與B-樹的處理一樣。在此,我就不多做介紹了。

總結

B+樹相比B樹的優勢: 
  1.單一節點儲存更多的元素,使得查詢的IO次數更少; 
  2.所有查詢都要查詢到葉子節點,查詢效能穩定; 
  3.所有葉子節點形成有序連結串列,便於範圍查詢。

原文:https://blog.csdn.net/z_ryan/article/details/79685072