强查找数据结构(2)-- 用于磁盘存储的B/B+树

一、磁盘存储

存储磁盘数据为什么用B/B+树这种数据结构?
在回答这个问题前,需要先考虑一个问题,二叉搜索树的层高对数据操作有哪些影响?
层高会影响数据的对比次数;对结点的寻址过程需要进行对比,层数越多对比次数也越多。
对于内存中的数据,用二叉搜索树或者红黑树存储和寻址时,由于内存对数据寻址操作速度很快,所以层高对结点寻址速度不会有显著影响。
但是对于磁盘中的数据,磁盘寻址速度很慢,层数过高导致寻址次数过多,将会显著降低结点数据的寻址速度。因此,对于磁盘的结点数据,需要用一种低层高的数据结构存储–B/B+树。

B/B+树是一种平衡多叉树,并且限制其所有的叶子结点都必须在同一层,这样保证了B/B+树是一种低层高的树。

二、什么是B/B+树

一棵M阶B树T的严格定义如下:
(1)每个结点至多有M棵子树(M阶就是一个结点的分叉数最多为M);
(2)根结点至少有两棵子树;
(3)除根结点外,其他每个分支结点至少要有M/2棵子树;
(4)所有的叶子结点都在同一层;
(5)如果一个分支结点有k棵子树,则该分支结点有k-1个关键字(关键字数量是分叉数量-1),关键字按照递增顺序进行排序;
(6)关键字数量n满足ceil(M/2)-1 <= n <= M-1; ceil()表示向上圆整。结合第3条和第5条性质自然可以推导出本条性质。
定义描述了B树的每个结点的分叉数量、每个结点的关键字数量和叶子节点的位置。B树所有节点的keys顺序关系类似于二叉搜索树,按照“从左到右”遍历时keys是有序的。

概念澄清:没有B-树,- 表示横杠而不是减号,所以没有B减树,B-树就是B树。
B树和B+树的区别:B树的所有结点都存储数据,但是B+树只有叶子结点存储数据,其他结点(内部节点)都用于索引。
B树和B+树的使用场景:B+树的存储数据的叶子节点存于磁盘中,而具有索引关系的内部节点存于内存中,索引节点快速寻址找到磁盘中的数据叶子节点;B树的存储数据的结点一部分存于内存中,一部分存于磁盘中。B+树的索引速度更快,现代数据库MySQL等使用的数据结构就是B+树。

三、B树的实现

B树及其结点定义

// SUB_M 表示 M/2
#define SUB_M  3 

typedef struct _btree_node { 
	int keys[2 * SUB_M - 1];
	btree_node *children[2 * SUB_M];
	
	int num; // 当前结点已存储数据的数量
	int leaf; // 当前结点是否为叶子结点

}btree_node;

typedef struct _btree {
	btree_node *root;
}btree;

或者

// 也可以将B树结点的关键字个数和分叉数写成不定值,由B树定义给出度的值
typedef struct _btree_node {
	int *keys;
	btree_node **children;
	
	int num; // 当前结点已存储数据的数量
	int leaf; // 当前结点是否为叶子结点

}btree_node;

typedef struct _btree {
	btree_node *root;
	int halfDegree;   // M/2
}btree;

四、 B树添加节点

由于B树一个节点有多个keys,为了区分节点和key,将添加新节点视为添加新key。
B树添加key的场景有两种:
(1)直接添加;如果添加新key后,被插入节点的keys数量不超过M-1,这时可以将新节key直接插入;
(2)先分裂再添加;如果添加新key后,被插入节点的keys数量超过M-1,不满足B树的性质,需要将被插入节点分裂后再插入,分裂点一般取中间点。增加到不能再增加时,先分裂再添加。
如图所示是”直接添加“和“先分裂再添加”两种场景:单个节点中keys数量上限是5,超过5个就分裂。
在这里插入图片描述
图1. 直接添加

在这里插入图片描述
图2. 先分裂再添加,由于被拆节点是根节点,拆分后需要创建一个新根节点和一个分裂出节点。此时拆出了3个节点

在这里插入图片描述
图3. 先分裂再添加,被拆节点不是根节点,拆分后需要创建一个分裂出节点,并将一个key上移到父节点;此时拆除两个结点,上移一个结点

五、B树删除节点

由于B树一个节点有多个keys,为了区分节点和key,将删除节点视为删除key。
B树删除key的场景有三种
(1)直接删除;如果删除key后,被删除key所在节点的keys数量 >= ceil(M/2)-1,满足B树性质,可以直接删除;
(2)先合并再删除;如果删除key后,被删除key所在节点的keys数量 < ceil(M/2)-1,不满足B树性质,不能直接删除,需要将该节点和同层的相邻节点合并后再删除(也就是删无再删时要先合并再删除);又因为合并后父节点的分叉减少,所以需要同时将父节点的一个key下移到合并后的节点中,注意这个key下移后要保持B树的keys依旧是顺序的。
(3)借位删除;如果合并导致父节点的keys数量 < ceil(M/2)-1,那么就需要让父节点依次从同层的叔父节点–>祖父节点–>父节点进行key的借位操作。当借无可借时,又会进行同层节点以及父节点的合并。
如果被删key属于叶子节点,按照上述三种场景进行删除;如果被删key不属于叶子节点,将它移位到叶子节点后删除(其他key也要随之移位)。
在这里插入图片描述
图4. 直接删除一个结点

在这里插入图片描述
图5. 删除结点前先合并
在这里插入图片描述图6. 删除结点前先借位再合并

文章参考与<零声教育>的C/C++linux服务期高级架构线上课学习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值