本文目录
- 一、B树的定义
- 二、B树的查找
- 三、B树的插入
- 四、B树的删除
系列目录
本文转载自:一文彻底弄懂B树和B+树 (xianzilei.cn)
一、B树的定义
B数也称为B-树,他是一棵多路平衡查找树。我们描述一棵B树时需要指定它的阶数,阶数表示了一个节点最多有多少的孩子节点,一般使用字母m表示阶数。当m取2时,就是我们常见的二叉搜索树。
一棵m阶的B数定义如下:
- 1)每个节点最多有
m-1
个关键字 - 2)根节点最少可以只有一个关键字
- 2)非根节点至少有
Math.ceil(m/2)-1
个关键字 - 4)每个节点的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它
- 5)所有叶子节点都位于同一层,或者说根节点到每个叶子节点的路径长度都相同
上图表示是一棵4阶B树(当然实际中B树的阶数一般远大于4,通常大于100,这样即使存储大量的数据,B树的高度仍然很低),每个节点最多有3个关键字,每个非根节点最少有Math.ceil(4/2)-1
=1个关键字。我们将一个key和其对应的data称为一个记录。数据库中如果以B树作为索引结构,此时B树中的key
就表示键,而data
表示了这个键对应的条目在硬盘上的逻辑地址。
二、B树的查找
以上图为例,比如我要查找关键字为25对应的数据,步骤如下:
- 1)首先拿到根节点关键字,目标关键字与根节点的关键字key比较,25<36,去往其左孩子节点查找
- 2)获取当前节点的关键字15和28,15<25<28,所有查询15和28的中间孩子节点
- 3)获取当前节点的关键字19和25,发现25=25,所以直接返回关键字和data数据(如果没有查询到则返回null)
三、B树的插入
插入操作是指插入一条记录,即(key, data)的键值对。如果B树中已存在需要插入的键值对,则用需要插入的新data替换旧的data。若B树不存在这个key,则一定是在叶子节点中进行插入操作。
3.1 基本步骤
- 1)根据key找到要插入的叶子节点位置,插入记录
- 2)判断当前节点key的个数是否小于等于
m-1
,如果是直接结束,否则进行第三步 - 3)以节点中间的key为中心分裂成左右两部分,然后将这个中间的key插入到父节点中,这个key的左子树指向分裂后的左半部分,这个key的右子支指向分裂后的右半部分,然后将当前节点指向父节点,继续进行第3步,直到处理完根节点。
3.2 图文说明
以5阶B树为例(5阶B树节点最多有4个关键字,最少有2个关键字,其中根节点最少可以只有一个关键字),从初始时刻依次插入数据。
-
1)在空数中插入39
-
2)继续插入22,97和41
此时根节点有4个关键字
-
3)继续插入53
此时发现该节点有5个关键字超过了最大允许的关键字个数4,所以以key为41为中心进行分裂,分裂后当前节点指向根节点,根节点的关键字为1,满足B数条件,插入操作结束,结果如下所示(注意,如果阶数是偶数,分裂时就不存在排序恰好在中间的key,那么我们选择中间位置的前一个key或中间位置的后一个key为中心进行分裂即可)
-
4)插入13,21,40
此时当前节点5个关键字,需要分裂,则以22为中心,22节点插入到其父节点中,分裂后当前节点指向根节点,根节点的关键字为2,满足B数条件,插入操作结束,结果如下所示
-
5)同理依次输入30,27, 33 ,36,35,34 ,24,29,结果如下所示
-
6)继续插入26
此时节点关键字等于5,以27为中心分裂,并将27插入到父节点中,分裂后当前节点指向根节点,如下所示
此时27的进位导致当前节点也需要分裂,则以33为中心进行分裂,结果如下
-
7)同理最后再依次插入17,28,29,31,32,结果如下图所示
3.3 总结
一般来说,对于确定的m和确定类型的记录,节点大小是固定的,无论它实际存储了多少个记录。但是分配固定节点大小的方法会存在浪费的情况,比如key为28和29所在的节点,还有2个key的位置没有使用,但是已经不可能继续在插入任何值了,因为这个节点的前序key是27,后继key是30,所有整数值都用完了。所以如果记录先按key的大小排好序,再插入到B树中,节点的使用率就会很低,最差情况下使用率仅为50%。
四、B树的删除
删除操作是指根据key删除记录,如果B树中的记录中不存对应key的记录,则删除失败。
4.1 基本步骤
- 1)如果当前需要删除的key位于非叶子节点上,则用后继key(这里的后继key均指后继记录的意思)覆盖要删除的key,然后在后继key所在的子支中删除该后继key。此时后继key一定位于叶子节点上,这个过程和二叉搜索树删除节点的方式类似。删除这个记录后执行第2步
- 2)该节点key个数大于等于
Math.ceil(m/2)-1
,结束删除操作,否则执行第3步。 - 3)如果兄弟节点key个数大于
Math.ceil(m/2)-1
,则父节点中的key下移到该节点,兄弟节点中的一个key上移,删除操作结束。 - 4)否则,将父节点中的key下移与当前节点及它的兄弟节点中的key合并,形成一个新的节点。原父节点中的key的两个孩子指针就变成了一个孩子指针,指向这个新节点。然后当前节点的指针指向父节点,重复上第2步。(有些节点它可能即有左兄弟,又有右兄弟,那么我们任意选择一个兄弟节点进行操作即可)
4.2 图文说明
以5阶B树为例(5阶B树节点最多有4个关键字,最少有2个关键字,其中根节点最少可以只有一个关键字)。初始时刻以上述插入操作的最终状态为例。
-
1)初始状态
-
2)删除节点21
删除后节点中的关键字个数仍然大于等2,所以删除结束。
-
3)继续删除27,此时27由于是非叶子节点,则由它的后继节点28替换27,再删除28,结果如下所示
此时发现叶子节点的个数小于2,而它的兄弟节点中有3个记录(当前节点还有一个右兄弟,选择右兄弟就会出现合并节点的情况,不论选哪一个都行,只是最后B树的形态会不一样而已),我们可以从兄弟节点中借取一个key。所以父节点中的28下移,兄弟节点中的26上移,删除结束。结果如下图所示
-
4)删除32,结果如下图所示
当前节点中只有一个key,而兄弟节点中也仅有2个key。所以只能让父节点中的30下移和这个两个孩子节点中的key合并,成为一个新的节点,当前节点的指针指向父节点。结果如下图所示
当前节点key的个数满足条件,故删除结束
-
5)接着删除key为40的记录,删除后结果如下图所示
同理,当前节点的记录数小于2,兄弟节点中没有多余key,所以父节点中的key下移,和兄弟(这里我们选择左兄弟,选择右兄弟也可以)节点合并,合并后的指向当前节点的指针就指向了父节点。如下图所示
同理,对于当前节点而言只能继续合并了,最后结果如下所示
合并后节点当前节点满足条件,删除结束。