B-Tree的初衷是希望通過減少存儲I / O操作來減少在計算機硬盤驅動器上花費的時間。該技術在數據庫和文件系統等計算機領域中發揮了很好的作用。B-Tree及其變體在數據存儲方面發揮著前所未有的重要作用。
什麼是B-Tree?
B-Tree是一種自平衡樹(即所有葉節點具有相同的高度級別)數據結構。但與其他如二叉樹,紅黑樹和AVL樹不只有2個子節點不同,B-Tree的節點具有2個以上的子節點。因此,有時它被稱為M-way分支樹,因為B-Tree中的節點可以具有M個子節點(M> = 2)。
根節點,內部節點和葉節點
內部節點是具有子節點的節點。內部節點位於樹的底部上方。在圖1中,節點[3 | 6],節點[12 | 15 | 19 | 26]和節點[33 | 36]是內部節點。
葉子節點是沒有子節點的節點。它們是樹底部的節點。在圖1中,節點[1 | 2],節點[4 | 5],節點[20 | 22 | 25]和節點[31 | 32]是一些葉節點。
B-Tree的根節點是一個特殊節點。B-Tree只有一個根節點,它位於樹的頂部。根據B-Tree中的項目數,根節點可以是內部節點或葉節點。節點[9 | 30]是圖1中B樹的根節點。
B-Tree節點的屬性
每個節點可以有一堆密鑰和一堆子節點(子節點),其中子節點數可以是0或其鍵的總數加1.讓我們考慮節點[x]。如果node [x]是葉子節點,那麼它將沒有任何子節點。如果它是一個內部節點,那麼它的子節點總數是n [x] + 1,其中n [x]是其鍵的總數。
B-Tree的約束
令t為B樹的最小度,其中t > = 2
約束#1:除根節點以外的每個節點必須至少有(t -1)個密鑰。它是B-Tree節點中鍵總數的下限。
約束#2:包含根節點的每個節點必須至多具有(2 t - 1)個密鑰。所以我們說如果節點有(2 t - 1)個鍵,那麼節點已滿。它是B-Tree節點中鍵總數的上限。
約束#3:每個內部節點(根節點除外)必須至少有t個子節點。每個內部節點(包括根節點)必須具有至多2 級的兒童。
約束#4:節點中的密鑰必須按升序存儲。例如,在圖1中,節點[12 | 15 | 19 | 按鍵12
約束#5:密鑰左側的子節點的所有密鑰必須小於該密鑰。在圖1中,位於密鑰30左側的子節點是節點[12 | 15 | 19 | 26],節點[10 | 11],節點[13 | 14],節點[16 | 18],節點[20 | 22 | 25]和節點[28 | 他們的鑰匙小於30。
約束#6:密鑰右側的子節點的所有密鑰必須大於該密鑰。例如,在圖1中,位於鍵9右側的子節點是節點[12 | 15 | 19 | 26],節點[10 | 11],節點[13 | 14],節點[16 | 18],節點[20 | 22 | 25]和節點[28 | 他們的鑰匙大於9。
對於約束#4和約束#5,通常鍵的左側的所有節點必須具有小於它的鍵。
對於約束#4和約束#6,通常鍵的右側的所有節點必須具有大於它的鍵。
在圖1中,B樹的最小度為3,因此其下界為2,其上界為5。
如果你密切關注圖1中B-Tree的例子,你會注意到任何節點中的一個鍵實際上是該鍵左側和右側較低級別節點中所有鍵的範圍分隔符。
讓我們看一下節點[9 | 30]所示,鍵9左側的下部節點的鍵(由藍色箭頭鏈接的節點中的鍵)小於9,鍵9右側的下部節點的鍵(節點中的鍵)由綠色箭頭鏈接的大於9。鍵30左側的較低節點的鍵(由綠色箭頭鏈接的節點中的鍵)小於30,右側的較低節點的鍵(由紅色箭頭鏈接的節點中的鍵)大於30。這種B-Tree模式使得鍵搜索類似於二叉樹的鍵搜索。
只要B樹操作不違反上述約束,它就會自動進行自我平衡。換句話說,約束是這樣設計的,以保持其平衡屬性。
最小度數(t)與B樹高度(h )成反比,增加t將減少h。但是,較大的t意味著在節點中花費更多時間來搜索密鑰和遍歷子節點。
B-Tree操作的概念和偽代碼
左右兄弟節點
節點的左右兄弟是其在同一級別的右側或左側的節點。
在圖2中,節點的左兄妹[18 | 22]是節點[3 | 12]和它的右兄弟是節點[33 | 36。節點[3 | 2]沒有左兄弟,它的右兄弟是節點[18 | 22。節點的左兄妹[33 | 36]是節點[18 | 並且它沒有正確的兄弟姐妹。
節點健的前置和後繼
在本文中,此處提到的前置節點和後繼節點僅適用於內部節點。
前置是左側子樹內的葉節點中數值最大的那個節點。
同樣的,後繼是右側子樹內的葉節點中數值最小的節點。
在圖3中,節點17的前置節點是[13 | 14 | 16] 因為健16其左側最大的鍵值。節點17的後繼者是節點[19 | 20 |21] 因為鍵值19是其右側的最小鍵值。
拆分節點
對於插入,如果我們要插入的節點已滿(有時也稱為溢出),我們需要拆分一個節點,使其不違反約束#2。
左右旋轉
對於刪除,從節點中刪除密鑰可能違反約束#1(有時也稱為下溢)。如果其中一個鍵的鍵數大於下限,我們可以將其左側或右側兄弟中的一個鍵移動(借用)到該節點。
與左右兄弟合併
如果無法左/右旋轉,則將節點與其兄弟節點合併。
實現B-tree操作的一般概念
更復雜的一些邏輯在於健值的插入,刪除動作。B-Tree鍵插入涉及拆分節點,而鍵刪除涉及旋轉和合並。
為了完成鍵值插入或刪除,我們需要遵守的一條重要原則是:在執行操作之前,目標鍵值必須位於葉子節點中。如果鍵值位於內部節點中,則需要先將其交換到葉節點中。
鍵值搜索
<code>
1. `Key-Search (searched-key)`
3. `Current-Processed-Node = Root-Node`
5. `While (Current-Processed-Node is not NULL)`
6. `Current-Index = 0`
8. `While ((Current-Index < key number of Current-Processed-Node) AND`
9. `(searched-key > Current-Processed-Node.Keys[Current-Index]))`
10. `Current-Index++`
11. `End While`
14. `If ((Current-Index < key number of Current-Processed-Node) AND`
15. `(searched-key == Current-Processed-Node.Keys[Current-Index]))`
16. `searched-key is found`
17. `Return it (We are done)`
19. `End If`
21. `Current-Processed-Node = Left/Right Child of Current-Processed-Node`
22. `End While`
24. `Return NULL`
/<code>
鍵插入
圖7:密鑰插入的示例
<code>
1. `Split-Node(parent-node, splitted-node)`
3. `Create new-node`
4. `Leaf[new-node] = Leaf[splitted-node] (The new node must have the same leaf info)`
6. `Copy right half of the keys from splitted-node to the new node`
8. `If (Leaf[splitted-node] is FALSE) Then`
9. `Copy right half of the child pointers from spitted-node to the new node`
10. `End If`
12. `Move some of parent children to the right accordingly`
14. `parent-node.children[relevant index] = new-node`
16. `Move some of parent keys to the right accordingly as well`
18. `Parent-node.keys[relevant index] = splitted-node.keys[the right-most index]`
/<code>
<code>
1. `Insert-Key-To-Node(current-node, inserted-key)`
3. `If (Leaf[current-node] == TRUE) Then`
4. `Put inserted-key in the node in ascending order`
5. `Return (We are done)`
6. `End If`
8. `Find the child-node where inserted-key belong`
10. `If (total number of keys in child-node == UPPER BOUND) Then`
11. `Split-Node(current-node, child-node)`
12. `Return Insert-Key-To-Node(current-node, inserted-key)`
13. `End If`
15. `Insert-Key-To-Node(child-node, inserted-key)`
/<code>
<code>
1. `Insert-Key(inserted-key)`
3. `If (root-node is NULL) Then`
4. `Allocate for root-node`
5. `Leaf[root-node] = TRUE`
6. `End If`
9. `If (total number of keys in root-node == UPPER BOUND) Then`
10. `Create a new-node`
11. `Assign root-node to be the child pointer of the new-node`
12. `Assign new-node to be the root-node`
13. `Split-Node(new-node, new-node.children[0])`
14. `End If`
16. `Insert-Key-To-Node(new-node, inserted-key)`
/<code>
其中,Split-Node()和Insert-Key-To-Node()是輔助函數,最終由Insert-Key()調用。根節點的更新由Insert-Key()處理,其餘部分由Split-Node()和Insert-Key-To-Node()處理。可以觀察到,健值的插入總是發生在葉子節點處。在插入路徑期間,如果節點已滿,我們需要執行拆分並在該點重新啟動插入過程。通過這樣做,我們確保B-Tree不會因鍵值插入產生溢出問題。
鍵值刪除
<code>
1. `Delete-Key-From-Node(parent-node, current-node, deleted-key)`
3. `If (Leaf[current-node] == TRUE) Then`
4. `Search for deleted-key in current-node`
6. `If (deleted-key not found) Then`
7. `Return (We are done)`
8. `End If`
10. `If (total number of keys in current-node > LOWER BOUND) Then`
11. `Remove the key in current-node`
12. `Return (We are done)`
13. `End If`
15. `Get left-sibling-node and right-sibling-node of current-node`
17. `If (left-sibling-node is found AND total number of keys in left-sibling-node > LOWER BOUND) Then`
18. `Remove deleted-key from current-node`
19. `Perform right rotation`
20. `Return (We are done)`
21. `End If`
23. `If (right-sibling-node is found AND total number of keys in right-sibling-node > LOWER BOUND) Then`
24. `Remove deleted-key from current-node`
25. `Perform left rotation`
26. `Return (We are done)`
27. `End If`
29. `If (left-sibling-node is not NULL) Then`
30. `Merge current-node with left-sibling-node`
31. `Else`
32. `Merge current-node with right-sibling-node`
33. `End If`
35. `Return Rebalance-BTree-Upward(current-node)`
36. `End If`
38. `Find predecessor-node of current-node`
40. `Swap the right-most key of predecessor-node and deleted-key of current-node`
42. `Delete-Key-From-Node(predecessor-parent-node, predecessor-node, deleted-key)`
/<code>
<code>
1. `Rebalance-BTree-Upward(current-node)`
3. `Create Stack`
5. `For each step of the path from root-node to current-node Then`
6. `Stack.push(step-node)`
7. `End For`
9. `While (Stack is not empty) Then`
10. `step-node = Stack.pop()`
11. `If (total number of keys in step-node < LOWER BOUND) Then`
12. `Rebalance-BTree-At-Node(step-node)`
13. `Else`
14. `Return (We are done)`
15. `End If`
16. `End While`
/<code>
<code>
1. `Rebalance-BTree-At-Node(step-node)`
3. `If (step-node is NULL OR step-node is root-node) Then`
4. `Return (We are done)`
5. `End If`
7. `Get left-sibling-node and right-sibling-node of step-node`
9. `If (left-sibling-node is found AND total number of keys in left-sibling-node > LOWER BOUND) Then`
10. `Remove deleted-key from step-node`
11. `Perform right rotation`
12. `Return (We are done)`
13. `End If`
15. `If (right-sibling-node is found AND total number of keys in right-sibling-node > LOWER BOUND) Then`
16. `Remove deleted-key from step-node`
17. `Perform left rotation`
18. `Return (We are done)`
19. `End If`
21. `If (left-sibling-node is not NULL) Then`
22. `Merge step-node with left-sibling-node`
23. `Else`
24. `Merge step-node with right-sibling-node`
25. `End If`
/<code>
<code>
1. `Delete-Key(deleted-key)`
3. `Delete-Key-From-Node(NULL, root-node, deleted-key)`
/<code>
根據[2],B-Tree中有兩種流行的鍵值刪除方式:
- 直接在tree的最低部執行操作:在進入節點之前先重構樹,這樣當我們實際從B-Tree中刪除一個鍵時,它的結構不會違反任何約束。刪除的偽代碼可以在[3]和[4]中找到。
- 查找並刪除鍵值。但是我們需要向上重新平衡,因為鍵值刪除會導致B-Tree的約束違規。我們的健值刪除實例基於此策略。刪除鍵值密鑰的主方法是Delete-Key()。稍後在Rebalance-BTree-Upward()被調用,以在必要時重組樹。重新平衡B樹從根本上涉及左/右旋轉和左/右兄弟合併。
顯示B-Tree的工具
最後,歡迎大家在文章下方留言進行評論,如果覺得本文寫的不錯,記得給小on 轉發、關注走一個哦! 下次再會~(* ̄▽ ̄ *)
閱讀更多 享學課堂online 的文章