B樹(B-樹)
1、B-樹(B樹)的基本概念
B-樹中所有結點中孩子結點個數的最大值成為B-樹的階,通常用m表示,從查找效率考慮,一般要求m>=3。一棵m階B-樹或者是一棵空樹,或者是滿足以下條件的m叉樹。
1)每個結點最多有m個分支(子樹);而最少分支數要看是否為根結點,如果是根結點且不是葉子結點,則至少要有兩個分支,非根非葉結點至少有ceil(m/2)個分支,這里ceil代表向上取整。
2)如果一個結點有n-1個關鍵字,那么該結點有n個分支。這n-1個關鍵字按照遞增順序排列。
3)每個結點的結構為:
其中,n為該結點中關鍵字的個數;ki為該結點的關鍵字且滿足ki<ki+1;pi為該結點的孩子結點指針且滿足pi所指結點上的關鍵字大於ki且小於ki+1,p0所指結點上的關鍵字小於k1,pn所指結點上的關鍵字大於kn。
4)結點內各關鍵字互不相等且按從小到大排列。
5)葉子結點處於同一層;可以用空指針表示,是查找失敗到達的位置。
注:平衡m叉查找樹是指每個關鍵字的左側子樹與右側子樹的高度差的絕對值不超過1的查找樹,其結點結構與上面提到的B-樹結點結構相同,由此可見,平衡m叉查找樹是B-樹,但限制更強,要求所有葉結點都在同一層。
光看上面的解釋可能大家對B-樹理解的還不是那么透徹,下面我們用一個實例來進行講解。
上面的圖片顯示了一棵B-樹,最底層的葉子結點沒有顯示。我們對上面提到的5條特點進行逐條解釋:
1)結點的分支數等於關鍵字數+1,最大的分支數就是B-樹的階數,因此m階的B-樹中結點最多有m個分支,所以可以看到,上面的一棵樹是一個5-階B-樹。
2)因為上面是一棵5階B-樹,所以非根非葉結點至少要有ceil(5/2)=3個分支。根結點可以不滿足這個條件,圖中的根結點有兩個分支。
3)如果根結點中沒有關鍵字就沒有分支,此時B-樹是空樹,如果根結點有關鍵字,則其分支數比大於或等於2,因為分支數等於關鍵字數+1.
4)上圖中除根結點外,結點中的關鍵字個數至少為2,因為分支數至少為3,分支數比關鍵字數多1,還可以看出結點內關鍵字都是有序的,並且在同一層中,左邊結點內所有關鍵字均小於右邊結點內的關鍵字,例 如,第二層上的兩個結點,左邊結點內的關鍵字為15,26,他們均小於右邊結點內的關鍵字39和45.
B-樹一個很重要的特征是,下層結點內的關鍵字取值總是落在由上層結點關鍵字所划分的區間內,具體落在哪個區間內可以由指向它的指針看出。例如,第二層最左邊的結點內的關鍵字划分了三個區間,小於 15,15到26,大於26,可以看出其下層中最左邊結點內的關鍵字都小於15,中間結點的關鍵字在15和26之間,右邊結點的關鍵字大於26.
5)上圖中葉子結點都在第四層上,代表查找不成功的位置。
2、B-樹的查找操作
B-樹的查找很簡單,是二叉排序樹的擴展,二叉排序樹是二路查找,B-樹是多路查找,因為B-樹結點內的關鍵字是有序的,在結點內進行查找時除了順序查找外,還可以用折半查找來提升效率。B-樹的具體查找步驟如下(假設查找的關鍵字為key):
1)先讓key與根結點中的關鍵字比較,如果key等於k[i](k[]為結點內的關鍵字數組),則查找成功
2)若key<k[1],則到p[0]所指示的子樹中進行繼續查找(p[]為結點內的指針數組),這里要注意B-樹中每個結點的內部結構。
3)若key>k[n],則道p[n]所指示的子樹中繼續查找。
4)若k[i]<key<k[i+1],則沿着指針p[I]所指示的子樹繼續查找。
5)如果最后遇到空指針,則證明查找不成功。
拿上面的二叉樹進行舉例,比如我們想要查找關鍵字42,下圖加粗的部分顯示了查找的路徑:
3、B-樹的插入
與二叉排序樹一樣,B-樹的創建過程也是將關鍵字逐個插入到樹中的過程。
在進行插入之前,要確定一下每個結點中關鍵字個數的范圍,如果B-樹的階數為m,則結點中關鍵字個數的范圍為ceil(m/2)-1 ~ m-1個。
對於關鍵字的插入,需要找到插入位置。在B-樹的查找過程中,當遇到空指針時,則證明查找不成功,同時也找到了插入位置,即根據空指針可以確定在最底層非葉結點中的插入位置,為了方便,我們稱最底層的非葉結點為終端結點,由此可見,B-樹結點的插入總是落在終端結點上。在插入過程中有可能破壞B-樹的特征,如新關鍵字的插入使得結點中關鍵字的個數超過規定個數,這是要進行結點的拆分。
接下來,我們以關鍵字序列{1,2,6,7,11,4,8,13,10,5,17,9,16,20,3,12,14,18,19,15}創建一棵5階B-樹,我們將詳細體會B-樹的插入過程。
(1)確定結點中關鍵字個數范圍
由於題目要求建立5階B-樹,因此關鍵字的個數范圍為2~4
(2)根結點最多可以容納4個關鍵字,依次插入關鍵字1、2、6、7后的B-樹如下圖所示:
(3)當插入關鍵字11的時候,發現此時結點中關鍵字的個數變為5,超出范圍,需要裂變(每次裂變出來的關鍵字都並到上級結點中),去關鍵字數組中的中間位置,也就是k[3]=6,作為一個獨立的結點,即新的根結點,將關鍵字6左、右關鍵字分別做成兩個結點,作為新根結點的兩個分支,此時樹如下圖所示:
(4)新關鍵字總是插在葉子結點上,插入關鍵字4、8、13之后樹為:
(5)關鍵字10需要插入在關鍵字8和11之間,此時又會出現關鍵字個數超出范圍的情況,因此需要進行裂變。並需要將裂變出來的關鍵字10納入根結點中,並將10左右的關鍵字做成兩個新的結點連在根結點上。插入關鍵字10並經過拆分操作后的B-樹如下圖:
(6)插入關鍵字5、17、9、16之后的B-樹如圖所示:
(7)關鍵字20插入在關鍵字17以后,此時會造成結點關鍵字個數超出范圍,需要裂變,方法同上,樹為:
(8)按照上述步驟依次插入關鍵字3、12、14、18、19之后B-樹如下圖所示:
(9)插入最后一個關鍵字15,15應該插入在14之后,此時會出現關鍵字個數超出范圍的情況,則需要進行裂變,將13並入根結點,13並入根結點之后,又使得根結點的關鍵字個數超出范圍,需要再次進行裂變,將10作為新的根結點,並將10左、右關鍵字做成兩個新結點連接到新根結點的指針上,這種插入一個關鍵字之后出現多次裂變的情況稱為連鎖反應,最終形成的B-樹如下圖所示:
4、B-樹的刪除
對於B-樹關鍵字的刪除,需要找到待刪除的關鍵字,在結點中刪除關鍵字的過程也有可能破壞B-樹的特性,如舊關鍵字的刪除可能使得結點中關鍵字的個數少於規定個數,這是可能需要向其兄弟結點借關鍵字或者和其孩子結點進行關鍵字的交換,也可能需要進行結點的合並,其中,和當前結點的孩子進行關鍵字交換的操作可以保證刪除操作總是發生在終端結點上。
我們用剛剛生成的B-樹作為例子,一次刪除8、16、15、4這4個關鍵字。
(1)刪除關鍵字8、16。關鍵字8在終端結點上,並且刪除后其所在結點中關鍵字的個數不會少於2,因此可以直接刪除。關鍵字16不在終端結點上,但是可以用17來覆蓋16,然后將原來的17刪除掉,這就是上面提到的和孩子結點進行關鍵字交換的操作。這里不能用15和16進行關鍵字交換,因為這樣會導致15所在結點中關鍵字的個數小於2。因此,刪除8和16之后B-樹如下圖所示:
(2)刪除關鍵字15,15雖然也在終端結點上,但是不能直接刪除,因為刪除后當前結點中關鍵字的個數小於2。這是需要向其兄弟結點借關鍵字,顯然應該向其右兄弟來借關鍵字,因為左兄弟的關鍵字個數已經是下限2.借關鍵字不能直接將18移到15所在的結點上,因為這樣會使得15所在的結點上出現比17大的關鍵字,所以正確的借法應該是先用17覆蓋15,在用18覆蓋原來的17,最后刪除原來的18,刪除關鍵字15后的B-樹如下圖所示:
(3)刪除關鍵字4,4在終端結點上,但是此時4所在的結點的關鍵字個數已經到下限,需要借關鍵字,不過可以看到其左右兄弟結點已經沒有多余的關鍵字可借。所以就需要進行關鍵字的合並。可以先將關鍵字4刪除,然后將關鍵字5、6、7、9進行合並作為一個結點鏈接在關鍵字3右邊的指針上,也可以將關鍵字1、2、3、5合並作為一個結點鏈接在關鍵字6左邊的指針上,如下圖所示:
顯然上述兩種情況下都不滿足B-樹的規定,即出現了非根的雙分支結點,需要繼續進行合並,合並后的B-樹如下圖所示:
有時候刪除的結點不在終端結點上,我們首先需要將其轉化到終端結點上,然后再按上面的各種情況進行刪除。在講述這種情況下的刪除方法之前,要引入一個相鄰關鍵字的概念,對於不在終端結點的關鍵字a,它的相鄰關鍵字為其左子樹中值最大的關鍵字或者其右子樹中值最小的關鍵字。找a的相鄰關鍵字的方法為:沿着a的左指針來到其子樹根結點,然后沿着根結點中最右端的關鍵字的右指針往下走,用同樣的方法一直走到葉結點上,葉結點上的最右端的關鍵字即為a的相鄰關鍵字(這里找的是a左邊的相鄰關鍵字,我們可以用同樣的思路找到a右邊的相鄰關鍵字)。可以看到下圖中a的相鄰關鍵字是d和e,要刪除關鍵字a,可以用d來取代a,然后按照上面的情況刪除葉子結點上的d即可。
5、B-樹的應用
為了將大型數據庫文件存儲在硬盤上,以減少訪問硬盤次數為目的,在此提出了一種平衡多路查找樹——B-樹結構。由其性能分析可知它的檢索效率是相當高的 為了提高 B-樹性能’還有很多種B-樹的變型,力圖對B-樹進行改進,比如B+樹。
B+樹
B+樹
B+樹是B-樹的變體,也是一種多路搜索樹:
1.其定義基本與B-樹同,除了:
2.非葉子結點的子樹指針與關鍵字個數相同;
3.非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間);
5.為所有葉子結點增加一個鏈指針;
6.所有關鍵字都在葉子結點出現;
如:(M=3)
B+的搜索與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中),其性能也等價於在關鍵字全集做一次二分查找;
B+的特性:
1.所有關鍵字都出現在葉子結點的鏈表中(稠密索引),且鏈表中的關鍵字恰好是有序的;
2.不可能在非葉子結點命中;
3.非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是存儲(關鍵字)數據的數據層;
4.更適合文件索引系統;
那么MySQL更適合哪個?B-Tree or B+Tree?
在講這兩種數據結構在數據庫中的選擇之前,我們還需要了解的一個知識點是操作系統從磁盤讀取數據到內存是以磁盤塊(block)為基本單位的,位於同一個磁盤塊中的數據會被一次性讀取出來,而不是需要什么取什么。即使只需要一個字節,磁盤也會從這個位置開始,順序向后讀取一定長度的數據放入內存。這樣做的理論依據是計算機科學中著名的局部性原理: 當一個數據被用到時,其附近的數據也通常會馬上被使用。
預讀的長度一般為頁(page)的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操作系統往往將主存和磁盤存儲區分割為連續的大小相等的塊,每個存儲塊稱為一頁(在許多操作系統中,頁得大小通常為4k)。
B-Tree和B+Tree該如何選擇呢?都有哪些優劣呢?
1、B-Tree因為非葉子結點也保存具體數據,所以在查找某個關鍵字的時候找到即可返回。而B+Tree所有的數據都在葉子結點,每次查找都得到葉子結點。所以在同樣高度的B-Tree和B+Tree中,B-Tree查找某個關鍵字的效率更高。
2、由於B+Tree所有的數據都在葉子結點,並且結點之間有指針連接,在找大於某個關鍵字或者小於某個關鍵字的數據的時候,B+Tree只需要找到該關鍵字然后沿着鏈表遍歷就可以了,而B-Tree還需要遍歷該關鍵字結點的根結點去搜索。
3、由於B-Tree的每個結點(這里的結點可以理解為一個數據頁)都存儲主鍵+實際數據,而B+Tree非葉子結點只存儲關鍵字信息,而每個頁的大小有限是有限的,所以同一頁能存儲的B-Tree的數據會比B+Tree存儲的更少。這樣同樣總量的數據,B-Tree的深度會更大,增大查詢時的磁盤I/O次數,進而影響查詢效率。
鑒於以上的比較,所以在常用的關系型數據庫中,都是選擇B+Tree的數據結構來存儲數據!下面我們以mysql的innodb存儲引擎為例講解,其他類似sqlserver、oracle的原理類似!
本文摘自:https://blog.csdn.net/qq_28584889/article/details/88777393