一、概述
1.歷史
B樹(B-Tree)結(jié)構(gòu)是一種高效存儲(chǔ)和查詢數(shù)據(jù)的方法,它的歷史可以追溯到1970年代早期。B樹的發(fā)明人Rudolf Bayer和Edward M. McCreight分別發(fā)表了一篇論文介紹了B樹。這篇論文是1972年發(fā)表于《ACM Transactions on Database Systems》中的,題目為"Organization and Maintenance of Large Ordered Indexes"。
這篇論文提出了一種能夠高效地維護(hù)大型有序索引的方法,這種方法的主要思想是將每個(gè)節(jié)點(diǎn)擴(kuò)展成多個(gè)子節(jié)點(diǎn),以減少查找所需的次數(shù)。B樹結(jié)構(gòu)非常適合應(yīng)用于磁盤等大型存儲(chǔ)器的高效操作,被廣泛應(yīng)用于關(guān)系數(shù)據(jù)庫(kù)和文件系統(tǒng)中。
B樹結(jié)構(gòu)有很多變種和升級(jí)版,例如B+樹,B*樹和SB樹等。這些變種和升級(jí)版本都基于B樹的核心思想,通過(guò)調(diào)整B樹的參數(shù)和結(jié)構(gòu),提高了B樹在不同場(chǎng)景下的性能表現(xiàn)。
總的來(lái)說(shuō),B樹結(jié)構(gòu)是一個(gè)非常重要的數(shù)據(jù)結(jié)構(gòu),為高效存儲(chǔ)和查詢大量數(shù)據(jù)提供了可靠的方法。它的歷史可以追溯到上個(gè)世紀(jì)70年代,而且在今天仍然被廣泛應(yīng)用于各種場(chǎng)景。
2.B-樹的優(yōu)勢(shì)
B樹和AVL樹、紅黑樹相比,B樹更適合磁盤的增刪改查,而AVL和紅黑樹更適合內(nèi)存的增刪改查。
假設(shè)存儲(chǔ)100萬(wàn)的數(shù)據(jù):
- 使用AVL來(lái)存儲(chǔ),樹高為:
(20次的磁盤IO很慢,但是20次的內(nèi)存操作很快)
- 使用B-樹存儲(chǔ),最小度數(shù)為500,樹高為:3
B樹優(yōu)勢(shì):
- 磁盤存儲(chǔ)比內(nèi)存存儲(chǔ)慢很多,尤其是訪問磁盤的延遲相對(duì)較高。每次訪問磁盤都需要消耗更多的時(shí)間,而B樹的設(shè)計(jì)可以最大化地減少對(duì)磁盤的訪問次數(shù)。
- 磁盤訪問一般是按塊讀取的,而B樹的節(jié)點(diǎn)通常設(shè)計(jì)為與磁盤塊大小一致。由于B樹是多路的,單次磁盤訪問通常會(huì)加載多個(gè)數(shù)據(jù)項(xiàng),而不是像AVL樹和紅黑樹那樣每次只讀取一個(gè)節(jié)點(diǎn)。
- 在磁盤中存儲(chǔ)B樹時(shí),操作系統(tǒng)通常會(huì)將樹的部分結(jié)構(gòu)加載到內(nèi)存中以便快速查詢,避免了頻繁的磁盤訪問。
- 在數(shù)據(jù)庫(kù)和文件系統(tǒng)中,數(shù)據(jù)通常是大規(guī)模的,存儲(chǔ)在外部存儲(chǔ)介質(zhì)上。B樹特別適合大規(guī)模數(shù)據(jù)的增刪改查,因?yàn)樗鼫p少了不必要的磁盤訪問,能夠高效地執(zhí)行復(fù)雜的數(shù)據(jù)操作。
二、特性
1.度和階
- 度(degree):節(jié)點(diǎn)的孩子數(shù)
- 階(order):所有節(jié)點(diǎn)孩子最大值
2.特性
-
每個(gè)節(jié)點(diǎn)具有
- 屬性 n,表示節(jié)點(diǎn)中 key 的個(gè)數(shù)
- 屬性 leaf,表示節(jié)點(diǎn)是否是葉子節(jié)點(diǎn)
- 節(jié)點(diǎn) key 可以有多個(gè),以升序存儲(chǔ)
每個(gè)非葉子節(jié)點(diǎn)中的孩子數(shù)是 n + 1、葉子節(jié)點(diǎn)沒有孩子
最小度數(shù)t(節(jié)點(diǎn)的孩子數(shù)稱為度)和節(jié)點(diǎn)中鍵數(shù)量的關(guān)系如下:
最小度數(shù)t | 鍵數(shù)量范圍 |
---|---|
2 | 1 ~ 3 |
3 | 2 ~ 5 |
4 | 3 ~ 7 |
... | ... |
n | (n-1) ~ (2n-1) |
其中,當(dāng)節(jié)點(diǎn)中鍵數(shù)量達(dá)到其最大值時(shí),即 3、5、7 ... 2n-1,需要分裂
- 葉子節(jié)點(diǎn)的深度都相同
三、實(shí)現(xiàn)
1.定義節(jié)點(diǎn)
static class Node {
// 關(guān)鍵字
int[] keys;
// 關(guān)鍵字?jǐn)?shù)量
int keyNum;
// 孩子節(jié)點(diǎn)
Node[] children;
// 是否是葉子節(jié)點(diǎn)
boolean leafFlag = true;
// 最小度數(shù):最少孩子數(shù)(決定樹的高度,度數(shù)越大,高度越小)
int t;
// ≥2
public Node(int t) {
this.t = t;
// 最多的孩子數(shù)(約定)
this.children = new Node[2 * t];
this.keys = new int[2 * t -1];
}
}
1.1 節(jié)點(diǎn)類相關(guān)方法
查找key:查找目標(biāo)22,在當(dāng)前節(jié)點(diǎn)的關(guān)鍵字?jǐn)?shù)組中依次查找,找到了返回;沒找到則從孩子節(jié)點(diǎn)找:
- 當(dāng)前節(jié)點(diǎn)是葉子節(jié)點(diǎn):目標(biāo)不存在
-
非葉子結(jié)點(diǎn):當(dāng)key循環(huán)到25,大于目標(biāo)22,此時(shí)從索引4對(duì)應(yīng)的孩子key數(shù)組中繼續(xù)查找,依次遞歸,直到找到為止。
image.png
根據(jù)key獲取節(jié)點(diǎn)
/**
* 根據(jù)key獲取節(jié)點(diǎn)
* @param key
* @return
*/
Node get(int key) {
// 先從當(dāng)前key數(shù)組中找
int i = 0;
while (i < keyNum) {
if (keys[i] == key) {
// 在當(dāng)前的keys關(guān)鍵字?jǐn)?shù)組中找到了
return this;
}
if (keys[i] > key) {
// 當(dāng)數(shù)組比當(dāng)前key大還未找到時(shí),退出循環(huán)
break;
}
i++;
}
// 如果是葉子節(jié)點(diǎn),沒有孩子了,說(shuō)明key不存在
if (leafFlag) {
return null;
} else {
// 非葉子節(jié)點(diǎn),退出時(shí)i的值就是對(duì)應(yīng)范圍的孩子節(jié)點(diǎn)數(shù)組的索引,從對(duì)應(yīng)的這個(gè)孩子數(shù)組中繼續(xù)找
return children[i].get(key);
}
}
向指定索引插入key
/**
* 向keys數(shù)組中指定的索引位置插入key
* @param key
* @param index
*/
void insertKey(int key,int index) {
/**
* [0,1,2,3]
* src:源數(shù)組
* srcPos:起始索引
* dest:目標(biāo)數(shù)組
* destPos: 目標(biāo)索引
* length:拷貝的長(zhǎng)度
*/
System.arraycopy(keys, index, keys, index + 1, keyNum - index);
keys[index] = key;
keyNum++;
}
向指定索引插入child
/**
* 向children指定索引插入child
*
* @param child
* @param index
*/
void insertChild(Node child, int index) {
System.arraycopy(children, index, children, index + 1, keyNum - index);
children[index] = child;
}
2.定義樹
public class BTree {
// 根節(jié)點(diǎn)
private Node root;
// 樹中節(jié)點(diǎn)最小度數(shù)
int t;
// 最小key數(shù)量 在創(chuàng)建樹的時(shí)候就指定好
final int MIN_KEY_NUM;
// 最大key數(shù)量
final int MAX_KEY_NUM;
public BTree() {
// 默認(rèn)度數(shù)設(shè)置為2
this(2);
}
public BTree(int t) {
this.t = t;
root = new Node(t);
MIN_KEY_NUM = t - 1;
MAX_KEY_NUM = 2 * t - 1;
}
}
判斷key在樹中是否存在
/**
* 判斷key在樹中是否存在
* @param key
* @return
*/
public boolean contains(int key) {
return root.get(key) != null;
}
3.新增key
- 1.查找插入位置:從根節(jié)點(diǎn)開始,沿著樹向下查找,直到找到一個(gè)葉子節(jié)點(diǎn),這個(gè)葉子節(jié)點(diǎn)包含的鍵值范圍覆蓋了要插入的鍵值。
- 2.插入鍵值:在找到的葉子節(jié)點(diǎn)中插入新的鍵值。如果葉子節(jié)點(diǎn)中的鍵值數(shù)量沒有超過(guò)B樹的階數(shù)(即每個(gè)節(jié)點(diǎn)最多可以包含的鍵值數(shù)量),則插入操作完成。
- 3.分裂節(jié)點(diǎn):如果葉子節(jié)點(diǎn)中的鍵值數(shù)量超過(guò)了B樹的階數(shù),那么這個(gè)節(jié)點(diǎn)需要分裂。
如果度為3,最大key數(shù)量為:2*3-1=5,當(dāng)插入了8后,此時(shí)達(dá)到了最大數(shù)量5,需要分裂:
分裂邏輯:
分裂節(jié)點(diǎn)數(shù)據(jù)一分為三:
- 左側(cè)數(shù)據(jù):本身左側(cè)的數(shù)據(jù)留在該節(jié)點(diǎn)
- 中間數(shù)據(jù):中間索引2(度-1)的數(shù)據(jù)6移動(dòng)到父節(jié)點(diǎn)的索引1(被分裂節(jié)點(diǎn)的索引)處。
- 右側(cè)數(shù)據(jù):從索引3(度)開始的數(shù)據(jù),移動(dòng)到新節(jié)點(diǎn),新節(jié)點(diǎn)的索引值為分裂節(jié)點(diǎn)的index+1
如果分裂的節(jié)點(diǎn)是非葉子節(jié)點(diǎn):
需要多一步操作:右側(cè)數(shù)據(jù)需要和孩子一起連帶到新節(jié)點(diǎn)去:
分裂的是根節(jié)點(diǎn):
需要再創(chuàng)建多一個(gè)節(jié)點(diǎn)來(lái)當(dāng)做根節(jié)點(diǎn),此根節(jié)點(diǎn)為父親,存入中間的數(shù)據(jù)。
其他步驟同上。
分裂方法:
/**
* 節(jié)點(diǎn)分裂
* 左側(cè)數(shù)據(jù):本身左側(cè)的數(shù)據(jù)留在該節(jié)點(diǎn)
* 中間數(shù)據(jù):中間索引2(度-1)的數(shù)據(jù)6移動(dòng)到父節(jié)點(diǎn)的索引1(被分裂節(jié)點(diǎn)的索引)處
* 右側(cè)數(shù)據(jù):從索引3(度)開始的數(shù)據(jù),移動(dòng)到新節(jié)點(diǎn),新節(jié)點(diǎn)的索引值為分裂節(jié)點(diǎn)的index+1
* @param node 要分裂的節(jié)點(diǎn)
* @param index 分裂節(jié)點(diǎn)的索引
* @param parent 要分裂節(jié)點(diǎn)的父節(jié)點(diǎn)
*
*/
public void split(Node node, int index, Node parent) {
// 沒有父節(jié)點(diǎn),當(dāng)前node為根節(jié)點(diǎn)
if (parent == null) {
// 創(chuàng)建出新的根來(lái)存儲(chǔ)中間數(shù)據(jù)
Node newRoot = new Node(t);
newRoot.leafFlag = false;
newRoot.insertChild(node, 0);
// 更新根節(jié)點(diǎn)為新創(chuàng)建的newRoot
this.root = newRoot;
parent = newRoot;
}
// 1.處理右側(cè)數(shù)據(jù):創(chuàng)建新節(jié)點(diǎn)存儲(chǔ)右側(cè)數(shù)據(jù)
Node newNode = new Node(t);
// 新創(chuàng)建的節(jié)點(diǎn)跟原本分裂節(jié)點(diǎn)同級(jí)
newNode.leafFlag = node.leafFlag;
// 新創(chuàng)建節(jié)點(diǎn)的數(shù)據(jù)從 原本節(jié)點(diǎn)【度】位置索引開始拷貝 拷貝長(zhǎng)度:t-1
System.arraycopy(node.keys, t, newNode.keys, 0, t - 1);
// 如果node不是葉子節(jié)點(diǎn),還需要把node的一部分孩子也同時(shí)拷貝到新節(jié)點(diǎn)的孩子中
if (!node.leafFlag) {
System.arraycopy(node.children, t, newNode.children, 0, t);
}
// 更新新節(jié)點(diǎn)的keyNum
newNode.keyNum = t - 1;
// 更新原本節(jié)點(diǎn)的keyNum
node.keyNum = t - 1;
// 2.處理中間數(shù)據(jù):【度-1】索引處的數(shù)據(jù) 移動(dòng)到父節(jié)點(diǎn)【分裂節(jié)點(diǎn)的索引】索引處
// 要插入父節(jié)點(diǎn)的數(shù)據(jù):
int midKey = node.keys[t - 1];
parent.insertKey(midKey, index);
// 3. 新創(chuàng)建的節(jié)點(diǎn)作為父親的孩子
parent.insertChild(newNode, index + 1);
// parent的keyNum在對(duì)應(yīng)的方法中已經(jīng)更新了
}
新增方法:
/**
* 新增key
*
* @param key
*/
public void put(int key) {
doPut(root, key, 0, null);
}
/**
* 執(zhí)行新增key
* 1.查找插入位置:從根節(jié)點(diǎn)開始,沿著樹向下查找,直到找到一個(gè)葉子節(jié)點(diǎn),這個(gè)葉子節(jié)點(diǎn)包含的鍵值范圍覆蓋了要插入的鍵值。
* 2.插入鍵值:在找到的葉子節(jié)點(diǎn)中插入新的鍵值。如果葉子節(jié)點(diǎn)中的鍵值數(shù)量沒有超過(guò)B樹的階數(shù)(即每個(gè)節(jié)點(diǎn)最多可以包含的鍵值數(shù)量),則插入操作完成。
* 3.分裂節(jié)點(diǎn):如果葉子節(jié)點(diǎn)中的鍵值數(shù)量超過(guò)了B樹的階數(shù),那么這個(gè)節(jié)點(diǎn)需要分裂。
* @param node 待插入元素的節(jié)點(diǎn)
* @param key 插入的key
* @param nodeIndex 待插入元素節(jié)點(diǎn)的索引
* @param nodeParent 待插入節(jié)點(diǎn)的父節(jié)點(diǎn)
*/
public void doPut(Node node, int key, int nodeIndex, Node nodeParent) {
// 查找插入位置
int index = 0;
while (index < node.keyNum) {
if (node.keys[index] == key ) {
// 找到了 做更新操作 (因?yàn)闆]有維護(hù)value,所以就不用處理了)
return;
}
if (node.keys[index] > key) {
// 沒找到該key, 退出循環(huán),index的值就是要插入的位置
break;
}
index++;
}
// 如果是葉子節(jié)點(diǎn),直接插入
if (node.leafFlag) {
node.insertKey(key, index);
} else {
// 非葉子節(jié)點(diǎn),繼續(xù)從孩子中找到插入位置 父親的這個(gè)待插入的index正好就是元素要插入的第x個(gè)孩子的位置
doPut(node.children[index], key , index, node);
}
// 處理節(jié)點(diǎn)分裂邏輯 : keyNum數(shù)量達(dá)到上限,節(jié)點(diǎn)分裂
if (node.keyNum == MAX_KEY_NUM) {
split(node, nodeIndex, nodeParent);
}
}
4.刪除key
情況一:刪除的是葉子節(jié)點(diǎn)的key
節(jié)點(diǎn)是葉子節(jié)點(diǎn),找到了直接刪除,沒找到返回。
情況二:刪除的是非葉子節(jié)點(diǎn)的key
沒有找到key,繼續(xù)在孩子中找。
找到了,把要?jiǎng)h除的key和替換為后繼key,刪掉后繼key。
平衡樹:該key被刪除后,key數(shù)目<key下限(t-1),樹不平衡,需要調(diào)整
-
如果左邊兄弟節(jié)點(diǎn)的key是富裕的,可以直接找他借:右旋,把父親的旋轉(zhuǎn)下來(lái),把兄弟的旋轉(zhuǎn)上去。
image.png -
如果右邊兄弟節(jié)點(diǎn)的key是富裕的,可以直接找他借:左旋,把父親的旋轉(zhuǎn)下來(lái),把兄弟的旋轉(zhuǎn)上去。image.png
-
當(dāng)沒有兄弟是富裕時(shí),沒辦法借,采用向左合并:父親和失衡節(jié)點(diǎn)都合并到左側(cè)的節(jié)點(diǎn)中
image.png
詳細(xì)右旋流程:
處理孩子:
向左合并詳細(xì)流程:
根節(jié)點(diǎn)調(diào)整的情況:
失衡調(diào)整代碼
/**
* 樹的平衡
* @param node 失衡節(jié)點(diǎn)
* @param index 失衡節(jié)點(diǎn)索引
* @param parent 失衡節(jié)點(diǎn)父節(jié)點(diǎn)
*/
public void balance(Node node, int index, Node parent) {
if (node == root) {
// 如果是根節(jié)點(diǎn) 當(dāng)調(diào)整到根節(jié)點(diǎn)只剩下一個(gè)key時(shí),要替換根節(jié)點(diǎn) (根節(jié)點(diǎn)不能為null,要保證右孩子才替換)
if (root.keyNum == 0 && root.children[0] != null) {
root = root.children[0];
}
return;
}
// 拿到該節(jié)點(diǎn)的左右兄弟,判斷節(jié)點(diǎn)是不是富裕的,如果富裕,則找兄弟借
Node leftBrother = parent.childLeftBrother(index);
Node rightBrother = parent.childRightBrother(index);
// 左邊的兄弟富裕:右旋
if (leftBrother != null && leftBrother.keyNum > MIN_KEY_NUM) {
// 1.要旋轉(zhuǎn)下來(lái)的key:父節(jié)點(diǎn)中【失衡節(jié)點(diǎn)索引-1】的key:parent.keys[index-1];插入到失衡節(jié)點(diǎn)索引0位置
// (這里父親節(jié)點(diǎn)旋轉(zhuǎn)走的不用刪除,因?yàn)榈葧?huì)左側(cè)的兄弟旋轉(zhuǎn)上來(lái)會(huì)覆蓋掉)
node.insertKey(parent.keys[index - 1], 0);
// 2.0 如果左側(cè)節(jié)點(diǎn)不是葉子節(jié)點(diǎn),有孩子,當(dāng)旋轉(zhuǎn)一個(gè)時(shí),只需要留下原本孩子數(shù)-1 ,把最大的孩子過(guò)繼給失衡節(jié)點(diǎn)的最小索引處(先處理后事)
if (!leftBrother.leafFlag) {
node.insertChild(leftBrother.removeRightMostChild(), 0);
}
// 2.1 要旋轉(zhuǎn)上去的key:左側(cè)兄弟最大的索引key,刪除掉,插入到父節(jié)點(diǎn)中【失衡節(jié)點(diǎn)索引-1】位置(此位置就是剛才在父節(jié)點(diǎn)旋轉(zhuǎn)走的key的位置)
// 這里要直接覆蓋,不能調(diào)插入方法,因?yàn)檫@個(gè)是當(dāng)初旋轉(zhuǎn)下去的key。
parent.keys[index - 1] = leftBrother.removeRightMostKey();
return;
}
// 右邊的兄弟富裕:左旋
if (rightBrother != null && rightBrother.keyNum > MIN_KEY_NUM) {
// 1.要旋轉(zhuǎn)下來(lái)的key:父節(jié)點(diǎn)中【失衡節(jié)點(diǎn)索引】的key:parent.keys[index];插入到失衡節(jié)點(diǎn)索引最大位置keyNum位置
// (這里父親節(jié)點(diǎn)旋轉(zhuǎn)走的不用刪除,因?yàn)榈葧?huì)右側(cè)的兄弟旋轉(zhuǎn)上來(lái)會(huì)覆蓋掉)
node.insertKey(parent.keys[index], node.keyNum);
// 2.0 如果右側(cè)節(jié)點(diǎn)不是葉子節(jié)點(diǎn),有孩子,當(dāng)旋轉(zhuǎn)一個(gè)時(shí),只需要留下原本孩子數(shù)-1 ,把最小的孩子過(guò)繼給失衡節(jié)點(diǎn)的最大索引處(孩子節(jié)點(diǎn)的索引比父親要多1)
if (!rightBrother.leafFlag) {
node.insertChild(rightBrother.removeLeftMostChild(), node.keyNum + 1);
}
// 2.1 要旋轉(zhuǎn)上去的key:右側(cè)兄弟最小的索引key,刪除掉,插入到父節(jié)點(diǎn)中【失衡節(jié)點(diǎn)索引-1】位置(此位置就是剛才在父節(jié)點(diǎn)旋轉(zhuǎn)走的key的位置)
// 這里要直接覆蓋,不能調(diào)插入方法,因?yàn)檫@個(gè)是當(dāng)初旋轉(zhuǎn)下去的key。
parent.keys[index] = rightBrother.removeLeftMostKey();
return;
}
// 左右兄弟都不夠,往左合并
if (leftBrother != null) {
// 向左兄弟合并
// 1.把失衡節(jié)點(diǎn)從父親中移除
parent.removeChild(index);
// 2.插入父節(jié)點(diǎn)的key到左兄弟 將父節(jié)點(diǎn)中【失衡節(jié)點(diǎn)索引-1】的key移動(dòng)到左側(cè)
leftBrother.insertKey(parent.removeKey(index - 1), leftBrother.keyNum);
// 3.插入失衡節(jié)點(diǎn)的key及其孩子到左兄弟
node.moveToTarget(leftBrother);
} else {
// 右兄弟向自己合并
// 1.把右兄弟從父親中移除
parent.removeChild(index + 1);
// 2.把父親的【失衡節(jié)點(diǎn)索引】 處的key移動(dòng)到自己這里
node.insertKey(parent.removeKey(index), node.keyNum);
// 3.把右兄弟完整移動(dòng)到自己這里
rightBrother.moveToTarget(node);
}
}
刪除key
/**
* 刪除指定key
* @param node 查找待刪除key的起點(diǎn)
* @param parent 待刪除key的父親
* @param nodeIndex 待刪除的key的索引
* @param key 待刪除的key
*/
public void doRemove(Node node, Node parent, int nodeIndex, int key) {
// 找到被刪除的key
int index = 0;
// 循環(huán)查找待刪除的key
while (index < node.keyNum) {
if (node.keys[index] >= key) {
//找到了或者沒找到
break;
}
index++;
}
// 如果找到了 index就是要?jiǎng)h除的key索引;
// 如果沒找到,index就是要在children的index索引位置繼續(xù)找
// 一、是葉子節(jié)點(diǎn)
if (node.leafFlag) {
// 1.1 沒找到
if (!found(node, key, index)) {
return;
}
// 1.2 找到了
else {
// 刪除當(dāng)前節(jié)點(diǎn)index處的key
node.removeKey(index);
}
}
// 二、不是葉子節(jié)點(diǎn)
else {
// 1.1 沒找到
if (!found(node, key, index)) {
// 繼續(xù)在孩子中找 查找的孩子的索引就是當(dāng)前index
doRemove(node.children[index], node, index, key);
}
// 1.2 找到了
else {
// 找到后繼節(jié)點(diǎn),把后繼節(jié)點(diǎn)復(fù)制給當(dāng)前的key,然后刪除后繼節(jié)點(diǎn)。
// 在索引+1的孩子里開始,一直往左找,直到節(jié)點(diǎn)是葉子節(jié)點(diǎn)為止,就找到了后繼節(jié)點(diǎn)
Node deletedSuccessor = node.children[index + 1];
while (!deletedSuccessor.leafFlag) {
// 更新為最左側(cè)的孩子
deletedSuccessor = deletedSuccessor.children[0];
}
// 1.2.1 當(dāng)找到葉子節(jié)點(diǎn)之后,最左側(cè)的key就是后繼key
int deletedSuccessorKey = deletedSuccessor.keys[0];
// 1.2.2 把后繼key賦值給待刪除的key
node.keys[index] = deletedSuccessorKey;
// 1.2.3 刪除后繼key 再調(diào)用該方法,走到情況一,刪除掉該后繼key: 起點(diǎn)為索引+1的孩子處,刪除掉后繼key
doRemove(node.children[index + 1], node, index + 1, deletedSuccessorKey);
}
}
// 樹的平衡:
if (node.keyNum < MIN_KEY_NUM) {
balance(node, nodeIndex, parent);
}
}
節(jié)點(diǎn)相關(guān)方法:
/**
* 移除指定索引處的key
* @param index
* @return
*/
int removeKey(int index) {
int deleted = keys[index];
System.arraycopy(keys, index + 1, keys, index, --keyNum - index);
return deleted;
}
/**
* 移除最左索引處的key
* @return
*/
int removeLeftMostKey(){
return removeKey(0);
}
/**
* 移除最右邊索引處的key
* @return
*/
int removeRightMostKey() {
return removeKey(keyNum - 1);
}
/**
* 移除指定索引處的child
* @param index
* @return
*/
Node removeChild(int index) {
Node deleted = children[index];
System.arraycopy(children, index + 1, children, index, keyNum - index);
children[keyNum] = null;
return deleted;
}
/**
* 移除最左邊的child
* @return
*/
Node removeLeftMostChild() {
return removeChild(0);
}
/**
* 移除最右邊的child
* @return
*/
Node removeRightMostChild() {
return removeChild(keyNum);
}
/**
* 獲取指定children處左邊的兄弟
* @param index
* @return
*/
Node childLeftBrother(int index) {
return index > 0 ? children[index - 1] : null;
}
/**
* 獲取指定children處右邊的兄弟
* @param index
* @return
*/
Node childRightBrother(int index) {
return index == keyNum ? null : children[index + 1];
}
/**
* 復(fù)制當(dāng)前節(jié)點(diǎn)到目標(biāo)節(jié)點(diǎn)(key和child)
* @param target
*/
void moveToTarget(Node target) {
int start = target.keyNum;
// 當(dāng)前節(jié)點(diǎn)不是葉子節(jié)點(diǎn) 說(shuō)明有孩子
if (!leafFlag) {
// 復(fù)制當(dāng)前節(jié)點(diǎn)的孩子到目標(biāo)節(jié)點(diǎn)的孩子中
for (int i = 0; i <= keyNum; i++) {
target.children[start + i] = children[i];
}
}
// 復(fù)制key到目標(biāo)節(jié)點(diǎn)的keys中
for (int i = 0; i < keyNum; i++) {
target.keys[target.keyNum++] = keys[i];
}
}