目标在叶节点
这种情况共有4
种,我们只需要考虑,删除节点后当前节点需要满足的。
b树的删除操作,都以这张图为例,这里为2-3阶b树:
目标删除后,该节点的键数量≥规定度数/2
直接删除即可,注意,需要让B树键值数量做自减。
如图:删除图中的叶节点节点中的3
.
目标删除后,该节点的键数量<规定度数/2
优先向兄弟节点(可以是左,也可以是右)借键。
如果是左,那么就借他的最大键;
如果是右,那么就借他的最小键;
得到之后,要记得对兄弟节点的键数量做自减
接着,再得到在父节点中,分离这两个节点的键,然后把这个键值赋给目标键所在的节点,删除目标键。
如图:本次删除的目标键为2
》》向兄弟借键时,需要考虑:
- 当前目标节点在父节点的孩子数组中的位置是啥?上图中
2
所在的5,9
节点中孩子数组的0索引处,这个时候,兄弟节点应该向右(父节点孩子数组索引为1的位置)。向右兄弟借键,就要借他的最小的键,否则,就违背了b树的定义。还有一个,就是取父节点的键值索引问题,因为向右节点借,需要取的是分离两个节点的键值,也就是5
,刚好在父节点键数组的索引0位置处。 - 假设现在删除
10
,此时,可以发现,10
所在的5,9
节点中孩子数组的最后一个(也就是父节点孩子数组索引为3,【n+1】,n表示节点的关键字数量,+1表示拥有孩子的个数),这个时候,就需要向左节点借max键。其次,就是父节点键的位置问题,这里的10
在父节点数组的最后一个,所以取得父节点分离键值就是当前节点索引-1处,即9
向兄弟节点借键,还有一种比较复杂的问题。记住,我会在最后一种情况进行阐述的。
-
若上述条件都不成立,那届时就处于一种很困的地步了。那就是合并了。
warnning:套路较多,请系好安全带,以免晕车哦
为避免晕车,我在这里先上一幅图,提前警备一下前方的坎坷路程。
举一个特别糟糕的例子,如图:
删除5
如图,一张图画不下,则会分开表示,请注意图的连续性哦。
总结 -
合并操作在叶节点的时候,只管合并,然后注意一下参与键值合并的节点的键值数量变化 ;
-
若在内部,重中之重的问题,就是还要合并两个节点的孩子,这里需要强调的是要严格遵循b树的定义,且看清楚兄弟节点的位置,然后进行父子相关联的操作
接下来就是有关于遇到父节点满足条件时进行合并的相关操作,与前面雷同,当在内部要进行这些操作时,也要进行两节点的孩子合并操作。但与借兄弟节点键不同,这个操作比借兄弟的要简单一些。
我们来回想一下,当删除的目标键在叶节点时,需要参照以下遵循:
1.当够就删;
2.当不够,就回溯;
3.若有兄弟可借酒,兄弟位置要准瞅,接着退出;
4.若兄弟不可借酒,就向父借酒,然后与兄弟共享之,但牢记要喊上孩子们,然后退出;
5.若实在都不行,则继续合并;
上述都是我个人自己编的,其实旨在于帮助理解。
目标不在叶节点
目标在内部节点,这个时候,就要找与它挨边儿的来逐渐下沉,交替。我还是画个图来感官的理解一下。
参照上面的那幅图 本次删除20
接下来的删除操作,就参照目标在叶节点来进行。
代码部分
具体代码:(主要用于处理合并的操作)
my_B_tree* addr[SONNODECOUNT] = { nullptr };//存储节点地址。
size_t keyArray[SONNODECOUNT] = { 0 };//存储键值
my_B_tree* mergeNode = nullptr;
while (1)
{
if ((parentNode->degress <= (DEGRESS >> 1) ||
parentNode->degress > (DEGRESS >> 1)) &&
brother->degress <= (DEGRESS >> 1))//合并
{
//此逻辑只能处理父节点是否满足删除后的键值是否满足1/2,并且兄弟节点不够借的情况。
mergeNode = new my_B_tree;
/*620--639 处理merge节点*/
NodesToOne(mergeNode, brother, findNode);//将兄弟和父里面的键值合并到merge
if (isRight == true) {
mergeNode->key[mergeNode->degress++] = parentNode->key[levelPos];
keyMobile(parentNode, levelPos);//重新将par节点的键对进行归位
}
else {
mergeNode->key[mergeNode->degress++] = parentNode->key[levelPos - 1];
keyMobile(parentNode, levelPos - 1);
}
int index = _insert_shellMode_Sort(mergeNode, key);//排序mergeNode节点
if (false == isDelete) {//delKey是否删除
keyMobile(mergeNode, index);
mergeNode->degress--;
isDelete = true;
}
/*对父节点进行移动,关联父与子节点的关系*/
if (isRight == true)
{
move_Bro_Par_addr(parentNode, levelPos);
parentNode->sonPt[levelPos] = mergeNode;//关联merge与parent的关系,将合并节点放在父节点的左边
}
else
{
move_Bro_Par_addr(parentNode, levelPos - 1);
parentNode->sonPt[levelPos - 1] = mergeNode;
}
parentNode->degress--;
//par指针回溯到原始B树的根节点,此时,merge节点中的parent指向为空
if (parentNode == tempRootNode)
{
mergeNode->parent = nullptr;
mergeAddr(mergeNode, findNode, brother, isRight);//将find孩子、par孩子合并至merge
FREE_MEMORY(parentNode);
tempRootNode = mergeNode;
FREE_MEMORY(findNode);
FREE_MEMORY(brother);
break;
}
else {//parent没有回到之前的临时根(tempRootNode)
mergeNode->parent = parentNode;
}
mergeAddr(mergeNode, findNode, brother);//将 findNode, brother两个孩子的节点合并到mergeNode
FREE_MEMORY(findNode);//释放findNode, brother
FREE_MEMORY(brother);
findNode = brother = nullptr;
//if (flag)//parent节点删除后依然满足1/2,退出
// break;
//find指针向上追溯
findNode = mergeNode->parent;
parentNode = parentNode->parent;
levelPos = searchAdd(findNode, parentNode);
if (levelPos == parentNode->degress) {
isRight = false;//brother为左兄弟 控制与那个兄弟节点进行交换
brother = parentNode->sonPt[levelPos - 1];
}
else
{
brother = parentNode->sonPt[levelPos + 1];
isRight = true;
}
}
else
{
//该逻辑只能处理单独向兄弟借的情况。
//获取findNode,brother节点的孩子键值以及地址,存储在容器中,并将键值进行排序
getSonNodeKey(findNode, brother, addr, keyArray, isRight);//error
size_t parKey, broKey;
modifyBroParent(parentNode, brother, &parKey, &broKey, levelPos, isRight);
//将parKey, broKey分别放入到par节点和bro节点
parentNode->key[parentNode->degress++] = broKey;
_insert_shellMode_Sort(parentNode, broKey);//父节点的键值进行排序
findNode->key[findNode->degress++] = parKey;
_insert_shellMode_Sort(findNode, parKey);
fdndResetConnect(findNode, addr);
break;
}
}
有关于沉底的操作,在这里就不提供代码了。