BTree简介:
BTree特性:
BTree的节点:
该节点类写在BTree的内部,里面包含基本的属性和一些后面插入和删除时的常用方法
static class Node {
int[] keys;//关键字
Node[] children;//孩子
int keyNumber;//有效关键字数目
boolean leaf = true;//是否是叶子节点
int t;//最小度数(最小孩子树) --由我们指定
public Node(int t) {//t>=2--最小孩子数为2
this.t = t;
this.children = new Node[2 * t];//最小度数*2==最多的孩子数--BTree约定
this.keys = new int[2 * t - 1];
}
@Override
public String toString() {
//把拷贝完成的数组转化为字符串输出
return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));
}
//多路查找
Node get(int key) {
//遍历
int i = 0;
while (i < keyNumber) {//有效范围内查询
if (keys[i] == key) {
return this;
}
// if (keys[i]<key){// <就继续循环
// i++;//继续向后找
// }
if (keys[i] > key) {//说明当前节点没找到
break;
}
i++;
}
//执行到此时 keys[i]>key i==keyNumber[出了范围]
//退出之后分两种情况:叶子节点,非叶子节点
//叶子节点
if (leaf) {
return null;
}
//非叶子节点--退出的时候找到了比key大的值--根据退出循环时i的值,找到相应的孩子,继续进行次操作--所以递归
return children[i].get(key);
}
//向keys指定索引处插入key
void insertKey(int key, int index) {
//从后往前移动元素
System.arraycopy(keys, index, keys, index + 1, keyNumber - index);
keys[index] = key;
keyNumber++;
}
//向children指定索引处加入child
void insertChild(Node child, int index) {
//从后往前移动元素
System.arraycopy(children, index, children, index + 1, keyNumber - index);
children[index] = child;
}
// 移除指定 index 处的 key
int removeKey(int index) {
int t = keys[index];
System.arraycopy(keys, index + 1, keys, index, --keyNumber - index);
return t;
}
// 移除最左边的 key
int removeLeftmostKey() {
return removeKey(0);
}
// 移除最右边的 key
int removeRightmostKey() {
return removeKey(keyNumber - 1);
}
// 移除指定 index 处的 child
Node removeChild(int index) {
Node t = children[index];
System.arraycopy(children, index + 1, children, index, keyNumber - index);
children[keyNumber] = null; // help GC
return t;
}
// 移除最左边的 child
Node removeLeftmostChild() {
return removeChild(0);
}
// 移除最右边的 child
Node removeRightmostChild() {
//孩子数目比keys数目多1
return removeChild(keyNumber);
}
// index 孩子处左边的兄弟
Node childLeftSibling(int index) {
return index > 0 ? children[index - 1] : null;
}
// index 孩子处右边的兄弟
Node childRightSibling(int index) {
return index == keyNumber ? null : children[index + 1];
}
// 复制当前节点的所有 key 和 child 到 target
void moveToTarget(Node target) {
int start = target.keyNumber;
if (!leaf) {//如果当前孩子不是叶子节点,说明它有孩子,
//就把当前节点的孩子,移动到target节点的孩子
for (int i = 0; i <= keyNumber; i++) {
//target原来有的孩子不覆盖【保留】,把自己的孩子追加进去
target.children[start + i] = children[i];
}
}
for (int i = 0; i < keyNumber; i++) {//key也追加进去
target.keys[target.keyNumber++] = keys[i];
}
}
}
BTree的插入:
插入:
split--分裂的分析:
叶子节点:
非叶子节点【比叶子节点多一个步骤,得处理它所带的孩子】:
根节点分裂【得创建出两个孩子,一个新root,一个right】
/**
* 2.新增
* 遇到叶子节点,直接加入key,如果key达到最大值,就执行分裂,重复这个过程、
* 分裂:一个节点中的数目到达2t-1时,就要执行分裂
*/
public void put(int key) {
doPut(root, key, null, 0);
}
private void doPut(Node node, int key, Node parent, int index) {
int i = 0;
while (i < node.keyNumber) {
if (node.keys[i] == key) {
return;//更新
}
if (node.keys[i] > key) {
break;//找到了插入位置,即为此时的i
}
i++;
}
if (node.leaf) {
node.insertKey(key, i);
//插入key之后,做一个检查,如果到达上限,那么就分裂
} else {//非叶子节点,找孩子插入,就是在第i个孩子插入,走相同的逻辑,所以递归
//要处理孩子节点,那么孩子节点的父亲,就是Node,要处理的孩子是第i个,所以传入的index==i
doPut(node.children[i], key, node, i);
//插入key之后,做一个检查,如果到达上限,那么就分裂
}
//插入key之后,做一个检查,如果到达上限,那么就分裂
if (node.keyNumber == MAX_KEY_NUMBER) {
split(node, parent, index);
}
}
/**
* 分裂方法的实现
*
* @param left 待分裂节点
* @param parent 父节点
* @param index 被分裂节点是第几个孩子
*/
private void split(Node left, Node parent, int index) {
if (parent == null) {//分裂的是根节点
Node newRoot = new Node(t);
newRoot.leaf = false;//都有孩子了,肯定不是叶子节点
newRoot.insertChild(left, 0);//旧根节点作为新根节点的0孩子
this.root = newRoot;
parent = newRoot;
}
//1.创建right节点,把left中t之后的key和child移动过去
Node right = new Node(t);//整个树的t都是一样的
//如果待分裂节点是叶子节点,那么新创建的节点一定是叶子节点
right.leaf = left.leaf;
//复制之后,left.keys数组不会被破坏,但是有效索引会发送变化
System.arraycopy(left.keys, t, right.keys, 0, t - 1);
if (!left.leaf) {//如果不是叶子节点,那么拷贝一些孩子过去
System.arraycopy(left.children, t, right.children, 0, t);
}
right.keyNumber = t - 1;//更新right数组的keyNumber属性
//实际上left中的keys[t-1]还在,只是打印时候不进行打印了
left.keyNumber = t - 1;//更新left数组的keyNumber属性--只拿较小的那一半key
//2.中间的key(t-1处)插入到父节点
int mid = left.keys[t - 1];
parent.insertKey(mid, index);
//3.right节点为父节点的孩子
parent.insertChild(right, index + 1);
}
BTree的删除:
删除【删除BTree中某个节点的key,不是把节点给删了】:
一共分为6种情况:
case1:当前节点是叶子节点,没找到,直接返回
case2:当前节点是叶子节点,找到了,直接删除
case3:当前节点是非叶子节点,没找到,要继续到它的孩子里查找
case4:当前节点是非叶子节点,找到了,不能直接删,找到后继的key,然后用后继的key,替换掉待删除的key,删除动作变成要删除后继的key了【因为已经进行了替换,现在树中有俩后继】--效果相当于删了被删除节点
case5:非根节点:删除后key数目
富余:node.keyNumber-1之后还>=MIN_KEY_NUMBER
如果左边兄弟有富余的情况,那么可以来一次右旋--就可以恢复平衡
如果右边兄弟有富余的情况,那么可以来一次左旋--就可以恢复平衡
左右两边都不够借的时候,就得进行合并【目的是让节点的数目减少一个】:
统一采用向左合并,位于右侧的节点就不要了,把他从父节点删掉,删掉之后把它和父节点的key统一合并到左侧节点上去【按升序顺序】--没有根了--特殊处理
case6:根节点--平衡调整时,针对根节点进行处理
在做了一些操作【比如合并】,根节点的keys可能可变0,原来的根没必要保存,children[0]替换掉旧的根
//3.删除
/**
* 删除【删除BTree中某个节点的key,不是把节点给删了】:
* case1:当前节点是叶子节点,没找到,直接返回
* case2:当前节点是叶子节点,找到了,直接删除
* case3:当前节点是非叶子节点,没找到,要继续到它的孩子里查找
* case4:当前节点是非叶子节点,找到了,不能直接删,找到后继的key,然后用后继的key,替换掉待删除的key,
* 删除动作变成要删除后继的key了【因为已经进行了替换,现在树中有俩后继】--效果相当于删了被删除节点 左旋,右旋,合并
* case5:非根节点--删除后key数目<下限【t-1】(不平衡)--要重新做平衡调整
* case6:根节点--平衡调整时,针对根节点进行处理
*
* @param key
*/
public void remove(int key) {
//先从根节点,并且从0索引开始找
doRemove(null, root, 0, key);
}
private void doRemove(Node parent, Node node, int index, int key) {
int i = 0;
while (i < node.keyNumber) {
if (node.keys[i] >= key) {
break;
}
i++;
}
//i 找到,代表待删除key的索引
//i 没找到:代表到第i个孩子继续查找
if (node.leaf) {//是叶子节点
if (!found(node, key, i)) {//case1:当前节点是叶子节点,没找到,直接返回
return;
} else {//case2:当前节点是叶子节点,找到了,直接删除
node.removeKey(i);//这里删完了不要return,因为还要检查是否<下限
}
} else {//不是叶子节点
if (!found(node, key, i)) {//case3:当前节点是非叶子节点,没找到,要继续到它的孩子里查找
doRemove(node, node.children[i], i, key);//进行递归调用即可
} else {
/*case4:当前节点是非叶子节点,找到了,不能直接删,找到后继的key,然后用后继的key,替换掉待删除的key,
删除动作变成要删除后继的key了【因为已经进行了替换,现在树中有俩后继】--效果相当于删了被删除节点*/
//怎么找后继key?
//1.找到后继key
Node s = node.children[i + 1];//比它大的孩子去找--s:后继key的起点
while (!s.leaf) {//只要不是叶子节点,就一直向左走
s = s.children[0];
}
//这是s就是后继key所在的节点
int sKey = s.keys[0];
//2.替换待删除key
node.keys[i] = sKey;
//3.删除后继key--操作相同,进行递归--以i+1的孩子处作为起点--删的key为后继key--sKey
doRemove(node, node.children[i + 1], i + 1, sKey);
}
}
//到这儿已经删除完成了,就看是否平衡
//判断key的数目是否<下限,<的话调整平衡
if (node.keyNumber < MIN_KEY_NUMBER) {
//调整平衡
//case5:非根节点--删除后key数目<下限【t-1】(不平衡)--要重新做平衡调整--左旋,右旋,合并
balance(parent, node, index);
//case6:根节点--平衡调整时,针对根节点进行处理
}
}
/**
* 平衡BTree---下面是调整,不是删除
*
* @param parent 被调整节点的父亲
* @param x 被调整节点
* @param i 被调整节点是父亲的第i个孩子
*/
private void balance(Node parent, Node x, int i) {
//case 6 根节点
if (x == root) {//根节点唯一不平衡的情况-->key的数目==0,或者孩子数==1
if (root.keyNumber == 0 && root.children[0] != null) {
root = root.children[0];
}
return;
}
Node left = parent.childLeftSibling(i);//左边兄弟
Node right = parent.childRightSibling(i);//右边兄弟
//case 5-1 左边富余,右旋
if (left != null && left.keyNumber > MIN_KEY_NUMBER) {
//a.右旋,把父节点【自左向右】最后一个比x小的旋转下去,旋转下去之后,在x中的索引为0
x.insertKey(parent.keys[i - 1], 0);//parent.keys[i-1]--父节点中被旋转的元素,添加到被调整的节点中
if (!left.leaf) {//说明左兄弟有孩子--要进行换孩子
//b.把左兄弟最右侧的孩子给被调整节点x的最左侧
x.insertChild(left.removeRightmostChild(), 0);
}
parent.keys[i - 1] = left.removeRightmostKey();//b.把左边兄弟最大的key旋转上去
return;
}
//case 5-2 右边富余,左旋
if (right != null && right.keyNumber > MIN_KEY_NUMBER) {
//a.父节点中后继key旋转下来
x.insertKey(parent.keys[i], x.keyNumber);
//b.right中最小的孩子换爹
if (!right.leaf) {
x.insertChild(right.removeLeftmostChild(), x.keyNumber + 1);//孩子索引比keys索引多1
}
//c.right中最小的key旋转上去
parent.keys[i] = right.removeLeftmostKey();
return;
}
//case 5-3 两边都不够借,向左合并
if (left != null) {
//向左兄弟合并
parent.removeChild(i);//从父亲删除被调整节点
//从父节点这儿把一个key给合并到左兄弟里面去
left.insertKey(parent.removeKey(i - 1), left.keyNumber);
x.moveToTarget(left);//把被调整节点和它的孩子都移动到左兄弟这边
} else {
//没有左兄弟,向自己合并--移除右兄弟,然后向他自己完成合并
parent.removeChild(i + 1);
x.insertKey(parent.removeKey(i), x.keyNumber);
right.moveToTarget(x);
}
}
private boolean found(Node node, int key, int i) {
return i < node.keyNumber && node.keys[i] == key;
}
测试代码:
import com.itheima.datastructure.btree.BTree;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TestBTree {
@Test
@DisplayName("split(t=2)")
void split1() {
/*
5 2|5
/ \ ==> / | \
1|2|3 6 1 3 6
*/
BTree tree = new BTree();
BTree.Node root = tree.root;
root.leaf = false;
root.keys[0] = 5;
root.keyNumber = 1;
root.children[0] = new BTree.Node(new int[]{1, 2, 3});
root.children[0].keyNumber = 3;
root.children[1] = new BTree.Node(new int[]{6});
root.children[1].keyNumber = 1;
tree.split(root.children[0], root, 0);
assertEquals("[2, 5]", root.toString());
assertEquals("[1]", root.children[0].toString());
assertEquals("[3]", root.children[1].toString());
assertEquals("[6]", root.children[2].toString());
}
@Test
@DisplayName("split(t=3)")
void split2() {
/*
6 3|6
/ \ ==> / | \
1|2|3|4|5 7 1|2 4|5 7
*/
BTree tree = new BTree(3);
BTree.Node root = tree.root;
root.leaf = false;
root.keys[0] = 6;
root.keyNumber = 1;
root.children[0] = new BTree.Node(new int[]{1, 2, 3, 4, 5});
root.children[0].keyNumber = 5;
root.children[1] = new BTree.Node(new int[]{7});
root.children[1].keyNumber = 1;
tree.split(root.children[0], root, 0);
assertEquals("[3, 6]", root.toString());
assertEquals("[1, 2]", root.children[0].toString());
assertEquals("[4, 5]", root.children[1].toString());
assertEquals("[7]", root.children[2].toString());
}
@Test
public void insert() {
BTree tree = new BTree();
tree.put(1);
tree.put(3);
tree.put(2);
}
@Test
@DisplayName("put(t=2)")
void testPut1() {
/*
2
/ \
1|2|3 => 1 3
2 2|4
/ \ => / | \
1 3|4|5 1 3 5
4
2|4 2|4|6 / \
/ | \ => / / \ \ => 2 6
1 3 5|6|7 1 3 5 7 / \ / \
1 3 5 7
4 4
/ \ / \
2 6 => 2 6|8
/\ / \ /\ / | \
1 3 5 7|8|9 1 3 5 7 9
4 4 4|8
/ \ / \ / | \
2 6|8 => 2 6|8|10 => 2 6 10
/\ / | \ /\ / / \ \ /\ /\ /\
1 3 5 7 9|10|11 1 3 5 7 9 11 1 3 5 7 9 11
*/
BTree tree = new BTree();
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.put(6);
tree.put(7);
tree.put(8);
tree.put(9);
tree.put(10);
tree.put(11);
assertEquals("[4, 8]", tree.root.toString());
assertEquals("[2]", tree.root.children[0].toString());
assertEquals("[6]", tree.root.children[1].toString());
assertEquals("[10]", tree.root.children[2].toString());
assertEquals("[1]", tree.root.children[0].children[0].toString());
assertEquals("[3]", tree.root.children[0].children[1].toString());
assertEquals("[5]", tree.root.children[1].children[0].toString());
assertEquals("[7]", tree.root.children[1].children[1].toString());
assertEquals("[9]", tree.root.children[2].children[0].toString());
assertEquals("[11]", tree.root.children[2].children[1].toString());
tree.travel();
}
@Test
@DisplayName("put(t=3)")
void testPut2() {
/*
3
/ \
1|2 4|5|6|7
*/
BTree tree = new BTree(3);
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.put(6);
tree.put(7);
assertEquals("[3]", tree.root.toString());
assertEquals("[1, 2]", tree.root.children[0].toString());
assertEquals("[4, 5, 6, 7]", tree.root.children[1].toString());
}
@Test
void testSearch() {
BTree tree = new BTree();
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.put(6);
tree.put(7);
tree.put(8);
tree.put(9);
tree.put(10);
assertEquals(tree.root, tree.root.get(4));
assertEquals(tree.root.children[0], tree.root.get(2));
assertEquals(tree.root.children[0].children[0], tree.root.get(1));
assertEquals(tree.root.children[0].children[1], tree.root.get(3));
assertEquals(tree.root.children[1], tree.root.get(6));
assertEquals(tree.root.children[1], tree.root.get(8));
assertEquals(tree.root.children[1].children[0], tree.root.get(5));
assertEquals(tree.root.children[1].children[1], tree.root.get(7));
assertEquals(tree.root.children[1].children[2], tree.root.get(9));
assertEquals(tree.root.children[1].children[2], tree.root.get(10));
assertNull(tree.root.get(11));
}
@Test
void testPut3() {
BTree tree = new BTree();
tree.put(6);
tree.put(3);
tree.put(8);
tree.put(1);
tree.put(2);
tree.put(5);
tree.put(4);
assertEquals("[4]", tree.root.toString());
assertEquals("[2]", tree.root.children[0].toString());
assertEquals("[6]", tree.root.children[1].toString());
assertEquals("[1]", tree.root.children[0].children[0].toString());
assertEquals("[3]", tree.root.children[0].children[1].toString());
assertEquals("[5]", tree.root.children[1].children[0].toString());
assertEquals("[8]", tree.root.children[1].children[1].toString());
}
@Test
@DisplayName("case1: leaf && not found")
public void testRemove0() {
/*
1|2|3|4
*/
BTree tree = new BTree(3);
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.remove(0);
tree.remove(8);
assertEquals("[1, 2, 3, 4]", tree.root.toString());
}
@Test
@DisplayName("case3: non-leaf && not found")
public void testRemove1() {
/*
3
/ \
1|2 4|5|6|7
*/
BTree tree = new BTree(3);
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.put(6);
tree.put(7);
tree.remove(0);
tree.remove(8);
assertEquals("[3]", tree.root.toString());
assertEquals("[1, 2]", tree.root.children[0].toString());
assertEquals("[4, 5, 6, 7]", tree.root.children[1].toString());
}
@Test
@DisplayName("case2: remove directly")
public void testRemove2() {
/*
3
/ \
1|2 4|5|6|7
*/
BTree tree = new BTree(3);
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.remove(3);
assertEquals("[1, 2, 4]", tree.root.toString());
tree.remove(1);
assertEquals("[2, 4]", tree.root.toString());
}
@Test
@DisplayName("case4: replace with successor")
public void testRemove3() {
/*
3
/ \
1|2 4|5|6|7
*/
BTree tree = new BTree(3);
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.put(6);
tree.put(7);
tree.remove(3);
assertEquals("[4]", tree.root.toString());
assertEquals("[1, 2]", tree.root.children[0].toString());
assertEquals("[5, 6, 7]", tree.root.children[1].toString());
}
@Test
@DisplayName("case5: balance right rotate")
public void testRemove4() { // 右旋
/*
4
/ \
1|2|3 5|6
*/
BTree tree = new BTree(3);
tree.put(1);
tree.put(2);
tree.put(4);
tree.put(5);
tree.put(6);
tree.put(3);
tree.remove(5);
assertEquals("[3]", tree.root.toString());
assertEquals("[1, 2]", tree.root.children[0].toString());
assertEquals("[4, 6]", tree.root.children[1].toString());
}
@Test
@DisplayName("case5: balance left rotate")
public void testRemove5() {
/*
3
/ \
1|2 4|5|6
4
/ \
1|3 5|6
*/
BTree tree = new BTree(3);
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.put(6);
tree.remove(2);
assertEquals("[4]", tree.root.toString());
assertEquals("[1, 3]", tree.root.children[0].toString());
assertEquals("[5, 6]", tree.root.children[1].toString());
}
@Test
@DisplayName("case5: balance merge a")
public void testRemove6() { // 合并
/*
3
/ \
1|2 4|5
*/
BTree tree = new BTree(3);
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.remove(4);
assertEquals("[1, 2, 3, 5]", tree.root.toString());
}
@Test
@DisplayName("case5: balance merge b")
public void testRemove7() { // 合并
/*
3
/ \
1|2 4|5
*/
BTree tree = new BTree(3);
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.remove(2);
assertEquals("[1, 3, 4, 5]", tree.root.toString());
}
@Test
@DisplayName("case6: from right to left")
void testRemove8() {
/*
4|8 4
/ | \ / \
2 6 10 => 2 6|8
/\ /\ /\ /\ / | \
1 3 5 7 9 11 1 3 5 7 9|10
4 4
/ \ / \
2 6|8 => 2 6
/\ / | \ /\ / \
1 3 5 7 9 1 3 5 7|8
4 4
/ \ / \
2 6 => 2 _ => 2|4
/\ /\ /\ / / | \
1 3 5 7 1 3 5|6 1 3 5|6
2|4 2
/ | \ => / \
1 3 5 1 3|4
*/
BTree tree = new BTree();
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.put(6);
tree.put(7);
tree.put(8);
tree.put(9);
tree.put(10);
tree.put(11);
tree.remove(11);
assertEquals("[4]", tree.root.toString());
assertEquals("[2]", tree.root.children[0].toString());
assertEquals("[6, 8]", tree.root.children[1].toString());
assertEquals("[1]", tree.root.children[0].children[0].toString());
assertEquals("[3]", tree.root.children[0].children[1].toString());
assertEquals("[5]", tree.root.children[1].children[0].toString());
assertEquals("[7]", tree.root.children[1].children[1].toString());
assertEquals("[9, 10]", tree.root.children[1].children[2].toString());
tree.remove(10);
assertEquals("[9]", tree.root.children[1].children[2].toString());
tree.remove(9);
assertEquals("[6]", tree.root.children[1].toString());
assertEquals("[5]", tree.root.children[1].children[0].toString());
assertEquals("[7, 8]", tree.root.children[1].children[1].toString());
tree.remove(8);
assertEquals("[7]", tree.root.children[1].children[1].toString());
tree.remove(7);
assertEquals("[2, 4]", tree.root.toString());
assertEquals("[1]", tree.root.children[0].toString());
assertEquals("[3]", tree.root.children[1].toString());
assertEquals("[5, 6]", tree.root.children[2].toString());
tree.remove(6);
assertEquals("[5]", tree.root.children[2].toString());
tree.remove(5);
assertEquals("[2]", tree.root.toString());
assertEquals("[1]", tree.root.children[0].toString());
assertEquals("[3, 4]", tree.root.children[1].toString());
tree.remove(4);
assertEquals("[3]", tree.root.children[1].toString());
tree.remove(3);
assertEquals("[1, 2]", tree.root.toString());
tree.remove(2);
assertEquals("[1]", tree.root.toString());
tree.remove(1);
assertEquals("[]", tree.root.toString());
}
@Test
@DisplayName("case6: from left to right")
void testRemove9() {
/*
4|8 4|8 8
/ | \ / | \ / \
2 6 10 => _ 6 10 => 4|6 10
/\ /\ /\ / /\ /\ / | \ /\
1 3 5 7 9 11 2|3 5 7 9 11 2|3 5 7 9 11
remove(2,3) => 8
/ \
6 10
/ \ /\
4|5 7 9 11
remove(4,5) => 8 => 8|10
/ \ / | \
_ 10 6|7 9 11
/ \ /\
6|7 9 11
remove(6,7) => 10
/ \
8|9 11
*/
BTree tree = new BTree();
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.put(6);
tree.put(7);
tree.put(8);
tree.put(9);
tree.put(10);
tree.put(11);
tree.remove(1);
assertEquals("[8]", tree.root.toString());
assertEquals("[4, 6]", tree.root.children[0].toString());
assertEquals("[10]", tree.root.children[1].toString());
assertEquals("[2, 3]", tree.root.children[0].children[0].toString());
assertEquals("[5]", tree.root.children[0].children[1].toString());
assertEquals("[7]", tree.root.children[0].children[2].toString());
assertEquals("[9]", tree.root.children[1].children[0].toString());
assertEquals("[11]", tree.root.children[1].children[1].toString());
tree.remove(2);
tree.remove(3);
assertEquals("[8]", tree.root.toString());
assertEquals("[6]", tree.root.children[0].toString());
assertEquals("[10]", tree.root.children[1].toString());
assertEquals("[4, 5]", tree.root.children[0].children[0].toString());
assertEquals("[7]", tree.root.children[0].children[1].toString());
assertEquals("[9]", tree.root.children[1].children[0].toString());
assertEquals("[11]", tree.root.children[1].children[1].toString());
tree.remove(4);
tree.remove(5);
assertEquals("[8, 10]", tree.root.toString());
assertEquals("[6, 7]", tree.root.children[0].toString());
assertEquals("[9]", tree.root.children[1].toString());
assertEquals("[11]", tree.root.children[2].toString());
tree.remove(6);
tree.remove(7);
assertEquals("[10]", tree.root.toString());
assertEquals("[8, 9]", tree.root.children[0].toString());
assertEquals("[11]", tree.root.children[1].toString());
tree.remove(8);
tree.remove(9);
assertEquals("[10, 11]", tree.root.toString());
assertTrue(tree.root.leaf);
tree.remove(10);
tree.remove(11);
assertEquals("[]", tree.root.toString());
}
@Test
@DisplayName("case6: delete middle")
void testRemove11() {
/*
4|8 5|8 5|8 8
/ | \ / | \ / | \ / \
2 6 10 => 2 6 10 => 2 _ 10 => 2|5 10
/\ /\ /\ /\ /\ /\ /\ | /\ / | \ /\
1 3 5 7 9 11 1 3 _ 7 9 11 1 3 6|7 9 11 1 3 6|7 9 11
8 9 9 5
/ \ / \ / \ / \
2|5 10 => 2|5 10 => 2|5 _ => 2 9
/ | \ /\ / | \ /\ / | \ | /\ / \
1 3 6|7 9 11 1 3 6|7 _ 11 1 3 6|7 10|11 1 3 6|7 10|11
5
/ \
2 9 =>
/\ / \
1 3 6|7 10|11
6 7 7
/ \ / \ / \
2 9 => 2 9 => 2 10
/\ / \ /\ / \ /\ / \
1 3 7 10|11 1 3 _ 10|11 1 3 9 11
7 9 9
/ \ / \ / \
2 10 => 2 10 => 2 _ => 2|9
/\ / \ /\ / \ /\ | / | \
1 3 9 11 1 3 _ 11 1 3 10|11 1 3 10|11
2|9 3|9 3|10
/ | \ => / | \ => / | \
1 3 10|11 1 _ 10|11 1 9 11
3|10 9|10 10
/ | \ => / | \ => / \
1 9 11 1 _ 11 1|9 11
10 11 9
/ \ => / \ => / \
1|9 11 1|9 _ 1 11
9 11
/ \ => / \ => 1|11
1 11 1 _
*/
BTree tree = new BTree();
tree.put(1);
tree.put(2);
tree.put(3);
tree.put(4);
tree.put(5);
tree.put(6);
tree.put(7);
tree.put(8);
tree.put(9);
tree.put(10);
tree.put(11);
tree.remove(4);
assertEquals("[8]", tree.root.toString());
assertEquals("[2, 5]", tree.root.children[0].toString());
assertEquals("[10]", tree.root.children[1].toString());
assertEquals("[1]", tree.root.children[0].children[0].toString());
assertEquals("[3]", tree.root.children[0].children[1].toString());
assertEquals("[6, 7]", tree.root.children[0].children[2].toString());
assertEquals("[9]", tree.root.children[1].children[0].toString());
assertEquals("[11]", tree.root.children[1].children[1].toString());
tree.remove(8);
assertEquals("[5]", tree.root.toString());
assertEquals("[2]", tree.root.children[0].toString());
assertEquals("[9]", tree.root.children[1].toString());
assertEquals("[1]", tree.root.children[0].children[0].toString());
assertEquals("[3]", tree.root.children[0].children[1].toString());
assertEquals("[6, 7]", tree.root.children[1].children[0].toString());
assertEquals("[10, 11]", tree.root.children[1].children[1].toString());
tree.remove(5);
assertEquals("[6]", tree.root.toString());
assertEquals("[2]", tree.root.children[0].toString());
assertEquals("[9]", tree.root.children[1].toString());
assertEquals("[1]", tree.root.children[0].children[0].toString());
assertEquals("[3]", tree.root.children[0].children[1].toString());
assertEquals("[7]", tree.root.children[1].children[0].toString());
assertEquals("[10, 11]", tree.root.children[1].children[1].toString());
tree.remove(6);
assertEquals("[7]", tree.root.toString());
assertEquals("[2]", tree.root.children[0].toString());
assertEquals("[10]", tree.root.children[1].toString());
assertEquals("[1]", tree.root.children[0].children[0].toString());
assertEquals("[3]", tree.root.children[0].children[1].toString());
assertEquals("[9]", tree.root.children[1].children[0].toString());
assertEquals("[11]", tree.root.children[1].children[1].toString());
tree.remove(7);
assertEquals("[2, 9]", tree.root.toString());
assertEquals("[1]", tree.root.children[0].toString());
assertEquals("[3]", tree.root.children[1].toString());
assertEquals("[10, 11]", tree.root.children[2].toString());
tree.remove(2);
assertEquals("[3, 10]", tree.root.toString());
assertEquals("[1]", tree.root.children[0].toString());
assertEquals("[9]", tree.root.children[1].toString());
assertEquals("[11]", tree.root.children[2].toString());
tree.remove(3);
assertEquals("[10]", tree.root.toString());
assertEquals("[1, 9]", tree.root.children[0].toString());
assertEquals("[11]", tree.root.children[1].toString());
tree.remove(10);
assertEquals("[9]", tree.root.toString());
assertEquals("[1]", tree.root.children[0].toString());
assertEquals("[11]", tree.root.children[1].toString());
tree.remove(9);
assertEquals("[1, 11]", tree.root.toString());
}
}