B树-删除

B树系列文章

1. B树-介绍

2. B树-查找

3. B树-插入

4. B树-删除

删除

根据B树的以下两个特性

  • 每一个非叶子结点(除根结点)最少有 ⌈ m/2⌉ 个子结点
  • 有k个子结点的非叶子结点拥有 k − 1 个键

因此,每个结点存放键的数量的下限的是m/2,

删除操作后减少结点的数量,可能导致导致结点”不饱和”

删除操作的重点和难点在于结点”不饱和”后的再平衡操作

先看下,结点”不饱和”后的再平衡处理

  • *如果缺少键结点的右兄弟存在且拥有多余的键,那么向左旋转

假设有一棵3阶B树,如下图所示

3阶B树每个结点的键的数量下限为1

B树-删除

这里删除20这个键,如下图所示

使得Node1结点键的数量为0,导致Node3″不饱和”

通过观察,Node1的右兄弟结点Node2有多余的结点,

即Node2结点的键数量大于1,即使减少一个也不会导致”不饱和”

B树-删除

Node1和Node2的分隔值40,这个分隔值大于Node1的所有键值(如果有到话) ,小于Node2的所有键值

因此,可以将父结点的键值下放到Node1,Node2结点的最小键值提升到父结点,使得Node1脱离”不饱和”状态,使得该B树恢复平衡状态

这个平衡操作,键值从右往左移动,因此也叫做左旋操作

  • *否则,如果缺少键结点的左兄弟存在且拥有多余的键,那么向右旋转

假设有一棵3阶B树,如下图所示

3阶B树每个结点的键的数量下限为1

B树-删除

这里删除90这个键,使得Node3结点键的数量为0,导致Node3″不饱和”

通过观察,Node3的左兄弟结点Node2有多余的结点,

即Node2结点的键数量大于1,即使减少一个也不会导致”不饱和”

B树-删除

Node3和Node2的分隔值70,这个分隔值大于Node2的所有键值,小于Node3的所有键值(如果有到话)

父结点的键值下放到Node3,Node2结点的最大键值提升到父结点,,使得Node3脱离”不饱和”状态,使得该B树恢复平衡状态

B树-删除

这个平衡操作,键值从左往右移动,因此也叫做右旋操作

  • *否则,如果它的两个直接兄弟结点都只有最小数量的键,那么将它与一个直接兄弟结点以及父结点中它们的分隔值合并

假设有一棵3阶B树,如下图所示

3阶B树每个结点的键的数量下限为1

B树-删除

这里删除55这个键,如下图所示

使得Node2结点键的数量为0,导致Node2″不饱和”

通过观察,Node2的左兄弟结点Node1和右兄弟结点Node3都没有多余的键

因此,可以让Node2与左右兄弟结点的任意一个结点进行合并,这里采用和右兄弟结点合并

B树-删除

将Node3结点合并到Node2的操作如下:

将Node2与Node3的在父结点中的分隔值,即70挪到Node2的末尾,

Node3结点的键值挪到Node2的末尾

最终结果如下图所示

B树-删除

问题:合并Node2和Node3是否会导致Node2″溢出”?

  1. 首先Node2减少一个键,恰好小于键数量的下限,即此时Node2的键个数为m/2-1

  2. Node2增加一个Node2与Node3的分隔值,此时数量为m/2

  3. Node3″不饱和”,即Node3的数量小于m/2

  4. 因此合并之后,Node2的键数量小于m,而Node2的键数量上限为m-1

  5. 所以不会”溢出”

前面的举例,B树是一个3阶B树,删除的都是叶子结点。

当要删除结点位于中间结点时,

可以选择一个新的分隔符(左子树中最大的键或右子树中最小的键),将它从叶子结点中移除,替换掉被删除的键作为新的分隔值。

这里可能导致叶子结点”不饱和”,因此可能需要从叶子结点开始进行再平衡操作

当中间结点”不饱和”时,旋转或者合并操作,在移动键值的时候,相应的儿子结点也需要同步移动

总结下来

删除操作的步骤如下

  • 搜索要删除的键值
  • 如果该键值在叶子结点,将它从中删除
  • 如果该叶子结点”不饱和”,按照后面 “删除后重新平衡”部分的描述重新调整树如果该键值在非叶子结点,我们注意到左子树中最大的键值仍然小于分隔值。同样的,右子树中最小的键值仍然大于分隔值
  • 选择一个新的分隔符(左子树中最大的键或右子树中最小的键值),将它从叶子结点中移除,替换掉被删除的键值作为新的分隔值。
  • 前一步删除了一个叶子结点中的键值。如果这个叶子结点拥有的键值数量小于最低要求,那么从这一叶子结点开始重新进行平衡。

删除后重新平衡

  • 如果缺少键结点的右兄弟存在且拥有多余的键,那么向左旋转
  • 将父结点的分隔值复制到缺少键的键值列表的最后(分隔值被移下来;缺少键的结点现在有最小数量的键)
  • 将右兄弟的第一个儿子结点复制到缺少键的儿子列表的最后
  • 右兄弟的第一个键覆盖父结点中的分隔值(右兄弟失去了一个结点但仍然拥有最小数量的键)
  • 右兄弟的键、儿子结点整体往前移动一个单位,即删除第一键和儿子
  • 树又重新平衡
  • 否则,如果缺少键结点的左兄弟存在且拥有多余的键,那么向右旋转
  • 缺少键结点的键、儿子结点整体往后移动一个单位
  • 将父结点的分隔值复制到缺少键结点的键列表的第一个位置(分隔值被移下来;缺少键的结点现在有最小数量的键)
  • 同时需要将左兄弟的最后一个儿子复制到缺少键结点的儿子列表的第一个位置
  • 左兄弟的最后一个键覆盖父结点中的分隔值(左兄弟失去了一个结点但仍然拥有最小数量的键)
  • 树又重新平衡
  • 否则,如果它的两个直接兄弟结点都只有最小数量的键,那么将它与一个直接兄弟结点以及父结点中它们的分隔值合并
  • 将分隔值复制到左边的结点的键值列表的最后(左边的结点可以是缺少键的结点或者拥有最小数量键的兄弟结点)
  • 将右边结点中所有的键值移动到左边结点的键值列表(左边结点现在拥有最大数量的键,右边结点为空)
  • 将右边结点中所有儿子结点移动到左边结点的儿子结点列表
  • 将父结点中的分隔值和空的右子树移除(父结点失去了一个键)
    • 如果父结点是根结点并且没有键了,那么释放它并且让合并之后的结点成为新的根结点(树的深度减小)
    • 否则,如果父结点的键数量小于最小值,重新平衡父结点

这里是删除的代码

B树-删除
* 删除结点内的某个key
/
func (bTreeNode
BTreeNode) removeKey(idx int) {
if bTreeNode.isLeaf { // 叶子结点
copy(bTreeNode.keyList[idx:], bTreeNode.keyList[idx+1:])
bTreeNode.keyNum
-= 1
}
else {
// 在叶子结点找一个元素替换掉被删除结点
leftChild := bTreeNode.leafList[idx]
rightChild :
= bTreeNode.leafList[idx+1]
var tmp
BTreeNode
if leftChild != nil {
// 从左儿子结点中找到最大的,替换掉被删除的key
tmp = leftChild
for !tmp.isLeaf {
tmp
= tmp.leafList[bTreeNode.keyNum]
}
bTreeNode.keyList[idx]
= tmp.keyList[tmp.keyNum-1]
tmp.keyNum
-= 1
}
else if rightChild != nil {
// 从右儿子结点找到最小的,替换掉被删除的key
tmp = rightChild
for !tmp.isLeaf {
tmp
= tmp.leafList[0]
}
bTreeNode.keyList[idx]
= tmp.keyList[0]
tmp.keyNum
-= 1
}
else {
fmt.Println(
"wrong!!!!")
}
}
}

/
* 左旋
/
func (bTreeNode
*BTreeNode) leftShift(idx int) {
pos :
= idx
if idx == 0 {
pos
= 1
}
curNode :
= bTreeNode.leafList[idx]
rightBro :
= bTreeNode.leafList[pos]
// 将父节点的分隔值复制到缺少元素节点的最后,(分隔值被移下来;缺少元素的节点现在有最小数量的元素)
curNode.keyList[bTreeNode.keyNum] = bTreeNode.keyList[idx]
curNode.keyNum
+= 1
// 兄弟结点的左儿子 作为 缺少元素节点 的右儿子
curNode.leafList[bTreeNode.keyNum] = rightBro.leafList[0]
</span><span>//</span><span> &#x5C06;&#x7236;&#x8282;&#x70B9;&#x7684;&#x5206;&#x9694;&#x503C;&#x66FF;&#x6362;&#x4E3A;&#x53F3;&#x5144;&#x5F1F;&#x7684;&#x7B2C;&#x4E00;&#x4E2A;&#x5143;&#x7D20;</span>
curNode.keyList[idx] = rightBro.keyList[<span>0</span><span>]

</span><span>//</span><span> &#x53F3;&#x5144;&#x5F1F;&#x7ED3;&#x70B9;&#x5C11;&#x4E86;&#x4E2A;&#x5143;&#x7D20;</span>
copy(rightBro.keyList, rightBro.keyList[<span>1</span><span>:])
copy(rightBro.leafList, rightBro.leafList[</span><span>1</span><span>:])
rightBro.keyNum </span>-= <span>1</span><span>

}

/
* 右旋
/
func (bTreeNode
*BTreeNode) rightShift(idx int) {
pos :
= idx
if pos == 0 {
pos
= 1
}
curNode :
= bTreeNode.leafList[idx]
leftBro :
= bTreeNode.leafList[pos-1]
// 当前结点数据往后一个位置
copy(bTreeNode.leafList[1:], bTreeNode.leafList)
copy(bTreeNode.keyList[
1:], bTreeNode.keyList)
// 将父节点的分隔值复制到缺少元素节点的第一个节点
curNode.keyList[0] = bTreeNode.keyList[idx]
curNode.keyNum
+= 1
curNode.leafList[
0] = leftBro.leafList[leftBro.keyNum] // 左兄弟的最后一个儿子放到当前结点的第一个儿子的位置

curNode.keyList[idx]
= leftBro.keyList[leftBro.keyNum-1] // 将父节点的分隔值替换为左兄弟的最后一个元素
leftBro.keyNum -= 1
}

/
* 合并
* 将分隔值及左右儿子结点合并
/
func (bTreeNode
BTreeNode) merge(curNode BTreeNode, idx int) {
pos :
= idx
if pos == 0 {
pos
= 1
}
leftBro :
= bTreeNode.leafList[pos-1]
rightBro :
= bTreeNode.leafList[pos]
// 将分隔值复制到左边的节点(左边的节点可以是缺少元素的节点或者拥有最小数量元素的兄弟节点)
leftBro.keyList[leftBro.keyNum] = bTreeNode.keyList[pos-1]
leftBro.keyNum
+= 1
// 将分隔值的右边节点中所有的元素移动到左边节点(左边节点现在拥有最大数量的元素,右边节点为空)
if rightBro != nil {
copy(leftBro.leafList[curNode.keyNum:], rightBro.leafList)
copy(leftBro.keyList[curNode.keyNum:], rightBro.keyList)
leftBro.keyNum
+= rightBro.keyNum
}
</span><span>//</span><span> &#x5C06;&#x7236;&#x8282;&#x70B9;&#x4E2D;&#x7684;&#x5206;&#x9694;&#x503C;&#x548C;&#x7A7A;&#x7684;&#x53F3;&#x5B50;&#x6811;&#x79FB;&#x9664;&#xFF08;&#x7236;&#x8282;&#x70B9;&#x5931;&#x53BB;&#x4E86;&#x4E00;&#x4E2A;&#x5143;&#x7D20;&#xFF09;</span>
copy(bTreeNode.keyList[pos-<span>1</span><span>:], bTreeNode.keyList[pos:])
copy(bTreeNode.leafList[pos:], bTreeNode.leafList[pos</span>+<span>1</span><span>:])
bTreeNode.keyNum </span>-= <span>1</span><span>

}

/
* 删除
/
func (bTreeNode
*BTreeNode) delete(key int, m int) {
// 找到第一个不比key小的,注意leaf的数量比key数量多1
idx := 0
for idx < bTreeNode.keyNum && key > bTreeNode.keyList[idx] { // 这里可以采用二分查找提高效率
idx++
}
curNode :
= bTreeNode.leafList[idx]
</span><span>if</span> idx &lt; bTreeNode.keyNum &amp;&amp; bTreeNode.keyList[idx] ==<span> key {
    </span><span>//</span><span> &#x5728;&#x5F53;&#x524D;&#x8282;&#x70B9;&#x627E;&#x5230;&#x4E86;key</span>

bTreeNode.removeKey(idx)
}
else if curNode != nil {
curNode.delete(key, m)
}

</span><span>/*</span><span>*
* &#x6BCF;&#x4E00;&#x4E2A;&#x975E;&#x53F6;&#x5B50;&#x8282;&#x70B9;&#xFF08;&#x9664;&#x6839;&#x8282;&#x70B9;&#xFF09;&#x6700;&#x5C11;&#x6709; &#x2308;m/2&#x2309; &#x4E2A;&#x5B50;&#x8282;&#x70B9;
* &#x6709; k &#x4E2A;&#x5B50;&#x8282;&#x70B9;&#x7684;&#x975E;&#x53F6;&#x5B50;&#x8282;&#x70B9;&#x62E5;&#x6709; k &#x2212; 1 &#x4E2A;&#x952E;
* &#x56E0;&#x6B64;&#x975E;&#x53F6;&#x5B50;&#x7ED3;&#x70B9;&#x6700;&#x5C11;&#x6709;m/2&#x4E2A;&#x952E;
*</span><span>*/</span>
<span>//</span><span> &#x53F6;&#x5B50;&#x8282;&#x70B9;&#x62E5;&#x6709;&#x7684;&#x5143;&#x7D20;&#x6570;&#x91CF;&#x5C0F;&#x4E8E;&#x6700;&#x4F4E;&#x8981;&#x6C42;</span>
<span>if</span> curNode != nil &amp;&amp; curNode.isLeaf &amp;&amp; curNode.keyNum &lt; m/<span>2</span> { <span>//</span><span> &#x9700;&#x8981;&#x8C03;&#x6574;</span>
    pos :=<span> idx
    </span><span>if</span> pos == <span>0</span><span> {
        pos </span>= <span>1</span><span>
    }
    leftBro :</span>= bTreeNode.leafList[pos-<span>1</span><span>]
    rightBro :</span>=<span> bTreeNode.leafList[pos]
    </span><span>if</span> rightBro != nil &amp;&amp; rightBro.keyNum &gt; m/<span>2</span> { <span>//</span><span> &#x7236;&#x7ED3;&#x70B9;key&#x5E76;&#x653E;&#x5230;&#x81EA;&#x5DF1;&#x7684;&#x6700;&#x540E; &#x53F3;&#x5144;&#x5F1F;&#x7684;&#x7B2C;&#x4E00;&#x4E2A;key&#x653E;&#x5230;&#x7236;&#x7ED3;&#x70B9;
        </span><span>//</span><span> &#x5982;&#x679C;&#x7F3A;&#x5C11;&#x5143;&#x7D20;&#x8282;&#x70B9;&#x7684;&#x53F3;&#x5144;&#x5F1F;&#x5B58;&#x5728;&#x4E14;&#x62E5;&#x6709;&#x591A;&#x4F59;&#x7684;&#x5143;&#x7D20;&#xFF0C;&#x90A3;&#x4E48;&#x5411;&#x5DE6;&#x65CB;&#x8F6C;</span>

bTreeNode.leftShift(idx)
}
else if leftBro != nil && leftBro.keyNum > m/2 { // 父结点key并放到自己的开头 左兄弟的最后一个key放到父结点
// 如果缺少元素节点的左兄弟存在且拥有多余的元素,那么向右旋转
bTreeNode.rightShift(idx)
}
else {
// 如果它的两个直接兄弟节点都只有最小数量的元素,那么将它与一个直接兄弟节点以及父节点中它们的分隔值合并
bTreeNode.merge(curNode, idx)
}
}
}

/
* 删除key
* 时间复杂度O(logn)
/
func (bTree
*BTree) Delete(key int) {
bTree.root.delete(key, bTree.m)
// 父节点的元素数量小于最小值,重新平衡父节点
if bTree.root.keyNum == 0 && bTree.root.leafList[0] != nil {
bTree.root
= bTree.root.leafList[0]
}
}
View Code

Original: https://www.cnblogs.com/amos01/p/16492348.html
Author: Amos01
Title: B树-删除

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/611208/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • SQL注入学习

    SQL注入学习——资源、笔记整理 OWASP-top10(2021) SQL注入产生原因:注入产生的原因是接受相关参数未经处理直接带入数据库查询操作;注入攻击属于服务端攻击,他与操…

    数据库 2023年6月9日
    074
  • 删除MySQL数据用户

    mysql删除用户的方法: 1、使用”drop user 用户名;”命令删除; 2、使用”delete from user where user…

    数据库 2023年6月14日
    081
  • 21粤比武

    先进行密码绕过,在这个界面迅速按下方向键,然后按下e进入编辑模式 找到linux16这一行,将lang编码后面的全部删掉,加上 <span class=”ne-text”&g…

    数据库 2023年6月11日
    096
  • 达梦产品技术支持培训-day8-DM8数据库备份与还原-实操

    Disql 工具:联机数据备份与还原,包括库备份、表空间备份与还原、表备份与还原; DMRMAN 工具:脱机数据库备份还原与恢复; 客户端工具 MANAGER和CONSOLE:对应…

    数据库 2023年6月11日
    073
  • 基于Redis&MySQL接口幂等性设计

    基于Redis&MySQL接口幂等性设计 欲把相思说似谁,浅情人不知。 幂等性即多次调用接口或方法不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。 前端重复提…

    数据库 2023年6月14日
    088
  • Pod控制器类型

    Pod是kubernetes的最小管理单元,在kubernetes中,按照pod的创建方式可以将其分为两类: 自主式pod:kubernetes直接创建出来的Pod,这种pod删除…

    数据库 2023年6月14日
    075
  • redis启动服务闪退,端口被占用

    1、首先查询一下redis端口的pid,使用命令【netstat -ano | findstr 端口号】redis默认端口号是6379 (注意!如果netstat命令使用不了的话,…

    数据库 2023年6月11日
    0115
  • markdown语法

    特殊字符对照表 点击查看特殊字符对照表 特殊字符 描述 字符代码 空格符 & 逻辑与 < 小于号 大于号 ¥ 人民币 ± 正负号 × 乘号 ÷ 除号 © 版权符号 ®…

    数据库 2023年6月6日
    068
  • mysql精简单机版,免登录,可复制,不启动服务,与本机mysql无冲突

    突然有了个需要在本地使用的mysql需求,要求不用安装,随拷随用,不影响其他mysql服务,占用空间小.基于这种需求做了个精简版的mysql 首先下载mysql的zip安装包 wi…

    数据库 2023年5月24日
    079
  • MySQL学习(3)—MySQL常用命令

    ps:此随笔基于mysql 5.7.*版本。 准备 net start mysql 启动MySQL服务 net stop mysql 关闭MySQL服务 mysql [-h exi…

    数据库 2023年5月24日
    082
  • jenkins 忘记密码

    仅适用centos7 一、 忘记密码 终端输入: vi /root/.jenkins/secrets/initialAdminPassword 复制文本内的密码,进行登录,此密码可…

    数据库 2023年6月14日
    075
  • 我设计数据库常用的几个原则

    以MySQL5.7为例,在一个项目中的数据库schema中建表 〇、建库 统一字符集和排序规则 规则 库的默认字符集选择utf8mb4,表、字段默认上级 库的排序规则选择utf8m…

    数据库 2023年6月9日
    0100
  • Mysql学习

    显示字符集编码 mysql架构 逻辑架构 Client : 提供连接MySQL服务器功能的常用工具集 Server : MySQL实例,真正提供数据存储和数据处理功能的MySQL服…

    数据库 2023年6月16日
    065
  • 用Python做一个中秋节嫦娥投食小游戏《千里婵娟》

    山河远阔,烟火人间,又一年,千里婵娟~ 今天给大家带来的是给玉兔投喂月饼的小游戏。八月十五中秋夜晚,让我们对着月亮许愿:希望我们在意和在意我们的人,诸邪避退、百事无忌、平安喜乐、万…

    数据库 2023年6月14日
    0100
  • 【StoneDB Class】入门第一课:数据库知识科普

    在没有出现数据库之前,数据存储在文本中,这种数据存储方式不管是管理还是查询,效率都是极其低下的,数据之间没有关联性。到了1970年,IBM研究员 E.F.Codd 发表了论文&#8…

    数据库 2023年5月24日
    085
  • MySQLB+树

    书名《MySQL是怎样运行的:从根儿上理解MySQL》。 这本书真的很好。如果你想学习,我建议你去😊看看。 [En] This book is really good. I sug…

    数据库 2023年5月24日
    097
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球