B-Tree (多路查找树)学习-20230503
- 前言
B-树是一类多路查询树,它主要用于文件系统和某些数据库的索引,如果采用二叉平衡树访问文件里面的数据,最坏情况下,磁头可能需要进行O(h)次对磁盘的读写,其中h为树的高度;此时如果采用B-树,由于它的多路访问特性可显著降低树的高度,所以对磁盘读写次数将大幅减少。
为了更清楚表述,我们引入经典的二次储存系统,为了增加储存容量,通常由多个磁盘构成,如果可以多路对磁盘进行访问,那么通过一次读取,很快可以定位数据的储存区域。
- B-Tree 的定义
根据《数据结构》(严蔚敏)定义一颗m阶的B-tree,或为空树,或满足下列特性的m叉树,
- 树中每个结点至多有m棵树,它定义节点中包含数据的上限值,此值和每个track的容量相关,由于B-tree的节点即储存键值,又储存子树指针,一般情况下键值会占据大量的数据空间,尤其是键值为结构体数据类型的时候,空间占据会急剧增加,为了应对这类挑战,人们发明了B+tree的数据结构
- 若根节点不是叶子节点,则至少有两棵树。此约束保证了B-tree不会退化为线性表,最坏情况下,允许退化为二叉树,这是最后的底线
- 除根节点之外的非终端节点至少有[m/2]棵子树,其中[m/2]取上限值
- 为了后续的程序操作方便,定义非终端结点包含下列数据信息的数据
( n , A 0 , K 1 , A 1 , K 2 , A 2 , . . . , K n , A n ) ; (n,A_0,K_1,A_1,K_2,A_2,...,K_n,A_n); (n,A0,K1,A1,K2,A2,...,Kn,An);
其中Ki为关键字,且K[i]<K[i+1],并且A[i-1]所指向的节点里面的关键字均小于K[i]关键字,A[i+1]所指向节点里面的关键字均大于K[i],n为关键字的数量([m/2]-1<=n<=m)
- 所有的叶子节点都在同一层次上,并且不带信息
- B-Tree 基本操作
3.1 查询操作
B-Tree树的查询操作与二叉查询树的查询操作类似。它实际上上分为两部分,第一部分需要找到值所在的节点的指针,然后在节点中可以采用顺序表搜索或者折半查找的方式定位待查询的值或者值所在的子树(指针),它是查找节点和在节点中搜索关键字的交叉进行的过程。
具体看一个例子,假定要查找key=99的值,从根节点出发,由于99>35,那么顺着A1指针指向的结点进行搜索,在A1指向的结点中,由于99>78,继续在A2所指的结点中搜索,在A2结点当中恰好K1=99,搜索完毕。
3.2 插入操作
B-Tree的生成是不断通过插入操作实现的,增加B-Tree高度唯一的途径是根节点的分裂,每次根节点分类事件发生,B-Tree的深度就增加1。除此之外,其它的插入也可能差生节点的分裂,但只要根节点不产生分裂,那么B-tree的深度就保持不变。
由于节点内关键字数目的限制,插入操作可能会导致节点分裂,这也导致了插入过程的复杂化。具体而言,插入某个值可以采用两个策略当中的任意一个,策略1 首先在最底层的某个非终端节点条件一个关键字,若该节点的数目不超过m-1,则插入完成,否则要产生节点的分裂,策略1实际上采用的是自下而上的方法,先从根节点出发,找到需要插入节点在最底层非终端节点上位置,然后执行插入,如果必要,则自下而上进行不断分裂。
具体看一个例子。对于m=3阶B-Tree,[m/2]=2。
假定需要依次插入关键字30,26,85和7,首先查找确定关键字应该插入的最底层结点的位置。通过查找得知,30应该插入在结点d所在位置,插入完成后,由于插入后的关键字数量小于m,无需任何分裂,插入作业完成。
同样查找关键字26亦应插入在d结点当中,由于d结点中关键在数目超过2,此时需要将d分裂成为两个结点,关键字26及其前后指针仍然保留在d结点中,而关键字37及其前后指针需要储存到新产生的结点d’当中。同时将中间关键字30和d’指针,一起插入到双亲结点中。由于更新后的b结点关键字未超过2,则插入完成。
结点d分裂为d和d’两个不同的结点。
类似地,在g中插入85后,需要分裂为两个结点,而当70插入到e结点当中去,由于e中的结点数目超过2,需要继续分裂;直到70插入到a结点中,插入结束。
85关键字插入后,g节点关键字数目不满足b-tree节点数目的基本要求,需要进行分裂,中间关键字70需要往移动到上一层节点e中去。由于70关键字的插入,导致 e结点关键字数量超过2,对于e结点需要继续分裂,中间关键字70继续往上移动至 结点a当中。
e结点分裂后的B-tree.
采用相同的思路,插入关键字7,通过查找关键字7应当插入至底层结点c当中,插入c后,由于c结点中的关键字数量大于2,需要分裂,关键字7移动至结点b当中,类似地,b结点中的关键字数量大于2;中间关键字24继续向上插入至根节点,由于根节点关键字数量大于2,根节点需要分裂,B-tree深度增加1,至此插入结束。
3.3 删除操作
B-Tree的删除操作比较复杂,其主要约束来自于B-tree特性的保持,一般情况下,则首先找到待删除关键字所在的结点,如果关键字所在结点为最下层的非终端节点,如果关键字数目不小于[m/2],直接删除即可,否则就需要自下而上进行结点的合并。倘若删除关键字为非终端结点Ki,则可以用指针Ai所指的树的最小关键字Y代替Ki,然后在相应的结点中删除Y即可。所以只需讨论删除最下层非终端结点的关键字即可。
有下列三种情况:
(1) 被删除关键字结点中的关键字数量不小于[m/2],则直接删除Ki和Ai即可,树的其它部分保持不变化。从树中删除关键字12就属于此类型。
删除12后,树的其它部分保持不变。
(2)被删除关键字所在的结点关键字数目为[m/2]-1,而与该节点相邻的右(左)兄弟结点的关键字数目大于[m/2]-1,则需将相邻右兄弟结点中最小(最大)的关键字上移至双亲结点中,而将双亲结点中小于(大于)且紧靠该上移关键字的关键字下移至被删除的结点当中。删除B-tree的关键字50便是如此情形。
(3)被删除关键字所在结点的左右子树关键字的数目都等于[m/2]-1, 假设该节点有右兄弟,且右兄弟结点由双亲结点中的Ai指针所指,那么在删除关键字之后,它所在的结点剩余的关键字和指针,另外加上双亲结点的Ki关键字合并到Ai所指的有兄弟结点中去。合并至左节点的逻辑亦相同。
删除关键字53,便是上述情形。
- 小结
由于B-tree的定义限制,导致 B-tree在插入和操作的时候需要分裂或合并结点,造成整体程序实现的复杂性。其实现方式通常采用自下而上,根据约束条件不断进行分裂或合并,直至根节点。另外B-tree深度增加的唯一途径就是根节点的分裂,B-tree深度减低的唯一途径就是根节点的合并。
参考文献:
并结点,造成整体程序实现的复杂性。其实现方式通常采用自下而上,根据约束条件不断进行分裂或合并,直至根节点。另外B-tree深度增加的唯一途径就是根节点的分裂,B-tree深度减低的唯一途径就是根节点的合并。
对于代码实现,如果有时间,我们将另外篇幅描述。
参考文献:
- 《数据结构》 严蔚敏