数据结构-B 树(B-树)

        在学习 B 树之前,我们先了解一下 B 树是为了解决什么问题的吧,在我们之前学习的数据结构中,处理的数据都是在内存中操作,因此考虑的都是内存的时间复杂度,但是如果我们要操作的数据集非常大,大到内存没办法处理了怎么办?例如数据库中上千万条记录的数据表,硬盘中上万个文件等等,在这种情况下,对数据处理需要不断的从硬盘存储设备中调入和调出内存页面,这种情况下,我们考虑的时间复杂度不仅仅是数据之间的比较运算操作了,我们必须考虑硬件等外部存储设备的访问时间以及将会对该设备做出多少次单独访问,而我们要设计的数据结构要尽量减少这种访问次数,所以就有了我们的 B 树。
        举个例子:有下面一颗平衡二叉树

        在这颗二叉树上我们去寻找 56 的话,需要去比较 4 次才能找到 56,也就是说如果这些数据存储在磁盘上的话,我们就需要单独访问磁盘 4 次,那么如果数据量很多很多呢,那么最坏的情况下我们需要对磁盘多少次,很显然是和平衡二叉树的高度相关的,那么,我们怎么去消除这种高度带来的访问效率问题呢?减少高度就行了,那么在元素不变的情况下怎么减少高度,第一可以让每个节点携带的数据元素变多,第二可以使分叉变多,平衡二叉树是二分叉,那么如果变成三分叉,四分叉就可以有效减少高度,所以 B 树也可以被称之为多路查找树。

B 树的阶

        B 树的阶是指 B 树的最大分叉路数,3 阶 B 数值这颗树最多可以有三条分支,也就是说所有节点最多能有三个孩子。

 B 树的结构设计

        以 3 阶 B 树结构设计为例:

typedef char TYPE;
typedef struct tnode
{
    TYPE date[2]; //节点元素,节点元素个数为阶数-1
    int height; //节点深度
    struct tnode *lchild; //左子树
    struct tnode *mchild; //中间子树
    struct tnode *rchild; //右子树
}TNode;

3 阶 B 树结构示例:

 

 以上都是 3 阶 B 树,可以看到一个节点有 2 个或 1 个元素,可以最多三个孩子

 B 树的属性

属性 1 .如果根节点不是叶节点,则其至少有两颗子树。
属性 2. 每一个非根的分支节点都有 k-1 个元素和 k 个孩子,其中[m/2]<=k<m。每一个叶子叶子节点 n 都有 k-1 个元素,其中[m/2]<k<m。(m 是 B 树的阶数)
属性 3 .所有叶子结点都位于同一层次。
属性 4. 所有分支结点包含下列信息数据(n,A 0 ,K 1 ,A 1 ,K 2 ,…K n A n ),其中:K i (i=1,2,…,n)为关键字, 且 Ki <K i+1 (i=1,2,…,n-1);A i (i=0,2,…,n)为指向子树根结点的指针,且指针 A i-1 所指子树中所有结点的关键字均小于 Ki (i=1,2,…,n),A n 所指子树中所有结点的关键字均大于Kn ,n*([m/2]-1<n<m-1)为关键字的个数(或 n+1 为子树的个数)。

B 树的插入实现

        因为有不同阶数的 B 树,所以我们以最常见的 3 阶 B 树为例,俗称 2-3 树的插入操作,与二叉树不同的是,B 树的插入操作有可能会对树的其余结构产生连锁反应。

3 阶 B 树的插入可分为三种情况

1)对于空树,插入一个 2 结点(结点中只有一个元素)即可,这个很简单

2)插入结点到一个 2 结点(结点中只有一个元素)的叶子上。应该说,由于其本身就只有
一个元素,所以只需要将其升级为 3 结点(结点中有两个元素)即可。如下图所示。
        插入元素 3,首先定位位置,3 比 8 小,向左走和 4 比,比 4 小,和 1 比,比 1 大,继续向下发现下面为空,因为属性 3 的原因,叶子结点要在同一高度,所以判断 1 这个结点是否可以扩充结点个数,显然可以,所以插入到 1 结点中,形成 1-3 结点。

 

3)要往 3 结点(结点中有两个元素)中插入一个新元素。因为 3 结点本身已经是 2-3 树的结点最大容量(已经有两个元素),因此就需要将其拆分,且将树中两元素或插入元素的三者中选择其一向上移动一层。复杂的情况也正在于此。
第一种情况 ,如下图
        结点 5 插入 6-7 结点,变成 5-6-7 结点,违背属性 2,结点元素个数不可以大于阶数-1,于是要向父节点送元素,5 又不能和 4 结点中,因为这样违背了属性 2,2 个元素应该有 3 个孩子,所以让 6 结点和 4 结点组成 4-6 结点,这样 5 结点就成为到 4-6 结点的第二个孩子。

 第二种情况如下图:

        结点 11 想插入 9-10,成为 9-10-11 结点,违背属性 2,于是向父节点送元素,和第一种情况一样送第二大的元素,于是父节点变成变成 10-12-14,父节点又违背属性2,然后继续向上到根结点,和第一种情况处理一样,把第二大的元素 12 给根结点,然后把 10-14 节点又拆分一半小的成为根节点的第二孩子。

 第三种情况如下图:

        还是和上面一样的步骤,元素满了将第二大的元素送上去,不过现在根节点也满了,然后继续向上送,成为一个新根节点,然后拆分,具体怎么拆的请通过观看作者的视频进行讲解,文字会比较难以接收,总之就是要满足 m 个元素要对应有 m+1 个孩子而已。

 B 树的删除实现

第一种情况 :所删除的元素位于一个 3 结点的叶子结点上,那么直接删除即可
第二种情况 :所删除的元素位于一个 2 结点上,这个情况就很复杂了,因为删除这样一个结点后的结构肯定会违背 B 树的属性,因此要分四种情况来处理:

 情形一:此结点的双亲是二结点,且拥有一个 3 结点的孩子,如下图

那么删除 1 结点后,只需要将 6-7 结点拆分,将 6 送上,然后换下 4 就行。
情形二:此结点的双亲是 2 结点,兄弟也是 2 结点,如下图

 此时首先要变成情形一的那种样子,在兄弟节点 7 上拉一个比 7 大一点的,显然是 8,然后找一个比 8 大一点的顶替上去就变成了情形一的样子,然后按照情形一的方式处理。

情形三:删除的结点双亲是一个三结点,如下图

 

        删除 10 意味着 12-14 这个结点不能成为 3 结点了,于是将这个结点删除,将最小的与左孩子结合成为 3 结点。

 

情形四:如果此时 B 树是一个满二叉树的情况,此时删除任何一个叶子结点都会使整个树不能满足要求,如下图:
        此时删除 8,那么 6 会和 7 合成一个结点,9 和 14 合成一个结点,13 为中间孩子,15为右孩子。
第三种情况 :所删除的元素位于非叶子结点。此时我们通常是将树按中序遍历后得到此元素的前驱或后继元素,考虑让它们来补位即可,例如下图

 

 

        当然,对于 B 树的插入和删除操作的所有情况没必要进行深入讲解,我们只需要理解该怎么去对他进行平衡操作就可以了,总的来说还是有一些规律的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九月丫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值