B树的增删改查

B-树,即为B树。因为B树的原英文名称为B-tree,而国内很多人喜欢把B-tree译作B-树,B-tree就是指的B树

B-树容易让人误解,建议大家用B树称呼, 本文以下直称B树

对概念不理解的可以参考 我的另一篇介绍B树概念和应用的博客: 

https://blog.csdn.net/q5706503/article/details/84729768

B树的基本概念

B树中结点允许拥有孩子结点个数的最大值成为B树的阶,通常用m表示,从查找效率考虑,一般要求m>=3。

一棵m阶B树或者是一棵空树,或者是满足以下条件的m叉树:

  • 每个节点最多有m个孩子(分支)。
  • 而最少分支数要看是否为根结点,如果是根结点且不是叶子结点,则至少要有两个分支,非根非叶结点至少有ceil(m/2)个分支,这里ceil代表向上取整。
  • 如果一个结点有n-1个关键字,那么该结点有n个分支。这n-1个关键字按照递增顺序排列。
  • 结点内各关键字按从小到大排列。
  • 叶子结点处于同一层;可以用空指针表示,是查找失败到达的位置。

每个结点的结构为:

nk1k2kn
p0p1p2pn

其中,n为该结点中关键字的个数;ki为该结点的关键字且满足ki<ki+1;pi为该结点的孩子结点指针且满足pi所指结点上的关键字大于ki且小于ki+1,p0所指结点上的关键字小于k1,pn所指结点上的关键字大于kn。

B-树是平衡m叉查找树,但限制更强,要求所有叶结点都在同一层。

举个例子:

这是一个4阶B树(m=4)

《B树的增删改查》

分析解释: 

  • 结点的分支数等于关键字数+1,最大的分支数就是B树的阶数,因此m阶的B树中结点最多有m个分支,所以可以看到,上面的一棵树是一个4阶B树
  • 因为上面是一棵4阶B树,所以非根非叶结点至少要有ceil(4/2)=2个分支。根结点可以不满足这个条件,图中的根结点有两个分支
  • 如果根结点中没有关键字就没有分支,此时B-树是空树,如果根结点有关键字,则其分支数比大于或等于2,因为分支数等于关键字数+1
  • 上图中除根结点外,结点中的关键字个数至少为1,因为分支数至少为2,分支数比关键字数多1,还可以看出结点内关键字都是有序的,并且在同一层中,左边结点内所有关键字均小于右边结点内的关键字,例如,第二层上的两个结点,右边结点内的关键字为11,15,他们均大于左边结点内的关键字4
  • B树一个很重要的特征是,下层结点内的关键字取值总是落在由上层结点关键字所划分的区间内,具体落在哪个区间内可以由指向它的指针看出
  • 上图中叶子结点都在同一层

 查找:

B-树的查找很简单,是二叉排序树的扩展,二叉排序树是二路查找,B-树是多路查找,因为B-树结点内的关键字是有序的,在结点内进行查找时除了顺序查找外,还可以用折半查找来提升效率。B-树的具体查找步骤如下(假设查找的关键字为key):
1)先让key与根结点中的关键字比较,如果key等于k[i](k[]为结点内的关键字数组),则查找成功
2)若key<k[1],则到p[0]所指示的子树中进行继续查找(p[]为结点内的指针数组),这里要注意B-树中每个结点的内部结构。
3)若key>k[n],则道p[n]所指示的子树中继续查找。
4)若k[i]<key<k[i+1],则沿着指针p[I]所指示的子树继续查找。
5)如果最后遇到空指针,则证明查找不成功。

拿上面的二叉树进行举例,比如我们想要查找关键字10,下图加粗的部分显示了查找的路径:

《B树的增删改查》

 

插入:

与二叉排序树一样,B-树的创建过程也是将关键字逐个插入到树中的过程。
在进行插入之前,要确定一下每个结点中关键字个数的范围,如果B-树的阶数为m,则结点中关键字个数的范围为ceil(m/2)-1 ~ m-1个。
对于关键字的插入,需要找到插入位置。在B-树的查找过程中,当遇到空指针时,则证明查找不成功,同时也找到了插入位置,即根据空指针可以确定在最底层非叶结点中的插入位置,为了方便,我们称最底层的非叶结点为终端结点,由此可见,B-树结点的插入总是落在终端结点上。在插入过程中有可能破坏B-树的特征,如新关键字的插入使得结点中关键字的个数超过规定个数,这是要进行结点的拆分

示例:

插入数据:{6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4}

《B树的增删改查》

删除:

删除步骤:

  • 搜索要删除的节点值
  • 若该节点为叶子节点 : 直接删除该数,  判断删除后是否失衡, 若失衡执行下面的失衡调整方法:
  • 若是非叶子节点 :  用相邻叶子节点 ( 左孩子的最大值/右孩子的最小值, 最后有介绍 ) 替换该节点值,  删除该替换节点值  问题就转换为了删除叶子节点的值
  • 失衡调整方法:
    1. 先向左右兄弟节点借值 , 借前判断左右兄弟节点的数量是否大于节点允许的最小个数(若节点内个数等于最小允许数则执行下面的方法)
    2.若左右兄弟节点的节点数不足 则 与兄弟节点(左右都可)与涉及到的父节点分割值合并, 合并之后判断父节点是否失衡(因为父节点中值个数-1可能失衡), 若失衡则对父节点递归调用该失衡调整方法

上述删除步骤不包含对根节点删除或节点很少时根节点的孩子删除的情况的判断(较为简单,自行拓展)

删除要考虑树中是否允许出现相同的数, 像上面的例子是可以可以重复的, 能否重复看具体需求, 最好不允许重复,因为有重复数据会使删除操作变复杂, 当然可以对重复数据删除很多次直到找不到该数据, 大家可能有更好的办法解决, 不过终究降低了删除的效率, 这里换一个没有重复数值的例子来示范删除:

《B树的增删改查》

该示例删除8、16、15、4这4个关键字。
(1)删除关键字8、16。关键字8在终端结点上,并且删除后其所在结点中关键字的个数不会少于2,因此可以直接删除。

关键字16不在终端结点上,但是可以用17来覆盖16,然后将原来的17删除掉,这就是上面提到的用相邻节点 ( 左孩子的最大值/右孩子的最小值 ) 替换该节点值操作。

这里不用15和16进行关键字交换,因为这样会导致15所在结点中关键字的个数小于2。因此,删除8和16之后B树如下图所示:

《B树的增删改查》

(2)删除关键字15,15虽然也在终端结点上 , 删除后当前结点中关键字的个数小于2。这里需要向其兄弟结点借关键字,显然应该向其右兄弟来借关键字,因为左兄弟的关键字个数已经是下限2.借关键字不能直接将18移到15所在的结点上,因为这样会使得15所在的结点上出现比17大的关键字,所以正确的借法应该是先用17覆盖15,在用18覆盖原来的17,最后删除原来的18,删除关键字15后的B-树如下图所示:

《B树的增删改查》

(3)删除关键字4,4在终端结点上,但是此时4所在的结点的关键字个数已经到下限,需要借关键字,不过可以看到其左右兄弟结点已经没有多余的关键字可借。

所以就需要与兄弟节点(左右都可)与涉及到的父节点分割值合并。先将关键字4删除,然后将关键字5、6(涉及到的父节点分割值)、7、9(兄弟节点)进行合并作为一个结点链接在关键字3右边的指针上,也可以将关键字1、2、3、5合并作为一个结点链接在关键字6左边的指针上,如下图所示:

《B树的增删改查》

显然上述两种情况下都使父节点失衡,即出现了非根的结点孩子数小于ceil(m/2),需要继续进行合并(对父元素递归失衡调整方法),合并后的B树如下图所示:

《B树的增删改查》

找a的相邻关键字的方法为:沿着a的左指针来到其子树根结点,然后沿着根结点中最右端的关键字的右指针往下走,用同样的方法一直走到叶结点上,叶结点上的最右端的关键字即为a的相邻关键字(这里找的是a左边的相邻关键字,我们可以用同样的思路找到a右边的相邻关键字)。可以看到下图中a的相邻关键字是d和e,要删除关键字a,可以用d来取代a,然后按照上面的情况删除叶子结点上的d即可。

B-树的高度及性能分析 

     B-树上操作的时间通常由存取磁盘的时间和CPU计算时间这两部分构成。B-树上大部分基本操作所需访问盘的次数均取决于树高h。关键字总数相同的情况下B-树的高度越小,磁盘I/O所花的时间越少。
     与高速的CPU计算相比,磁盘I/O要慢得多,所以有时忽略CPU的计算时间,只分析算法所需的磁盘访问次数(磁盘访问次数乘以一次读写盘的平均时间(每次读写的时间略有差别)就是磁盘I/O的总时间)。

1、B-树的高度
     定理9.1 若n≥1,m≥3,则对任意一棵具有n个关键字的m阶B-树,其树高h至多为:
        logt((n+1)/2)+1。
这里t是每个(除根外)内部结点的最小度数,即
        《B树的增删改查》 
     由上述定理可知:B-树的高度为O(logtn)。于是在B-树上查找、插入和删除的读写盘的次数为O(logtn),CPU计算时间为O(mlogtn)。

2、性能分析
  ①n个结点的平衡的二叉排序的高度H(即lgn)比B-树的高度h约大lgt倍。
     【例】若m=1024,则lgt=lg512=9。此时若B-树高度为4,则平衡的二叉排序树的高度约为36。显然,若m越大,则B-树高度越小。
  ②若要作为内存中的查找表,B-树却不一定比平衡的二叉排序树好,尤其当m较大时更是如此。
     因为查找等操作的CPU计算时间在B-树上是
        O(mlogtn)=0(lgn·(m/lgt))
而m/lgt>1,所以m较大时O(mlogtn)比平衡的二叉排序树上相应操作的时间O(lgn)大得多。因此,仅在内存中使用的B-树必须取较小的m。(通常取最小值m=3,此时B-树中每个内部结点可以有2或3个孩子,这种3阶的B-树称为2-3树)。

 

例子有参考以下地址文章

https://www.jianshu.com/p/7dedb7ebe033

插入的第一张图片和gif动图来自以下博主:(这个动图真的牛逼)

http://www.cnblogs.com/yangecnu/p/Introduce-B-Tree-and-B-Plus-Tree.html

    原文作者:B树
    原文地址: https://blog.csdn.net/q5706503/article/details/84729052
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞