二叉搜索树不适合应用到磁盘上,因为它的扇出数较低并且平衡时需要大量的节点重定位和指针更新。B树通过增加每个节点存储项的数量(高扇出)和减少频繁的平衡操作来解决这些问题。下面我们将讨论了B树的内部结构,B树的查找、插入和删除操作算法概要,以及用于保持B树平衡的拆分和合并操作。

B树实际上是平衡二叉树的扩展,不同之处在于B树具有更大的扇出数(即更多的子节点)和更低的树高。前文讨论二叉树时节点以圆形表示,而B树节点通常以矩形表示,当然二叉树也可以使用矩形来表示。图2-7使用矩形的方式表示二叉树,2-3树和B树,从中我们可以看出它们之间的相似性和差异性。

图2-7

B树的节点也是有序排列的,因此B树可以像二叉搜索树一样进行节点查找。这也就是说B树查找节点的时间复杂度是对数的,通过32次比较就能从包含40亿个节点的B树中找到某个键。

对于磁盘数据结构,如果每次比较都要经过磁盘扇区定位,这样的性能显然是不能接受的。但是每个B树节点存储几十甚至上百个条目,这就避免了每次比较都需要定位新的磁盘扇区,仅仅在进入B树下一层的时候才需要重定位加载新扇区。后面我们会更加详细的讨论查询算法的细节。

B树上可以非常高效的查询单个数据或者是查询某个范围内的数据。在查询语言里(如SQL),查询某个数据通常表示为等于(=),而查询某个范围数据通常表示为比较(, ≤和≥)。

B树的层次结构

B树由多个节点构成,每个节点又包含N个键和N+1个指向子节点的指针。从逻辑上节点可以分成3类:

根节点(Root Node): 根节点没有父节点,位于树的最顶部;

叶子节点(Leaf Nodes): 树的最底层节点,而且没有任何子节点。

内部节点(Internal Nodes): 所有连接根节点和叶子节点的节点,通常树包含多层内部节点。

图2-9

由于B树是一种磁盘页面组织技术(即用于组织固定大小的页面),通常一个节点即是一个磁盘页面,术语节点和页面通常是同一概念。节点容量与它实际持有的键数之间的关系称为占用率。

B树的显著特征是高扇出数(每个节点有较多的子节点)。高扇出数减少了为维持树平衡而需要做的结构改动,也可以减少查询时磁盘扇区定位的时间。B树只有在节点空或满的情况下才触发平衡操作(即分裂和合并)。

分隔键(Separator Keys)

B树节点上半部分存储的键称为分隔键。它们把树划分成子树,每个子树包含相应范围内键的节点。分隔键以有序的方式保存在节点内,因此节点内能通过二分法进行键查找;找到键对应区间后,沿着对应的指针指向的子树进入B树的下一层。

B树节点的下半部分保存了指向子树的指针,第一个指针指向保存了所有小于第一个分隔键的子树,最后一个指针指向了保存所有大于或等于最后一个分隔键的子树,其它中间指针指向包含了所有大于等于其左侧分隔键同时小于右侧分隔键节点的子树。 如图2-10。

图2-10

B树的查复杂度

B树查询的复杂性需要从两个方面考虑:磁盘页传输的数量和查找时进行键比较的数量。

在磁盘页传输数量方面,每个节点的分隔键划分当前搜索空间为原来的1/N。因此在从根节点到叶子节点的遍历过程中,需要读取磁盘页的数量为B树的层数,即B树的高度。

在键比较次数方面,每个节点中以二分法进行搜索,每一次比较都将当前搜索空间减半,因此复杂度为log₂M (M为节点的数目)。

B树的查询

为了从B树上查询某个键,我们需要从根节点到叶子节点遍历B树。首先在根节点保存的分隔键上执行二分查找,找到第一个大于查询键的分隔键,找到该分隔键对应的子树;在子树上重复二分查询操作,直到到达目标叶子节点。这时我们要么找到了需要查询的键,或者查询的键不存在。

查询时我们从最粗粒度的层次(树根)开始查找,然后进入到粒度更细的下一层,其中每层分隔键表示更精确、更详细的范围。重复这个过程,直到最后到达叶节点,即数据记录所在的位置。

单键查询时,在找到查询键时或确定没有查询键后搜索即告结束;而在范围查询时,在找到最接近范围开始的键值对后继续沿着它的兄弟节点查询,直到到达查询范围的末尾。

B树节点的拆分

当插入记录到B树时,首先需要找到插入目标位置,使用前一节中描述的查找算法即可找到目标位置;键值被附加到找到的叶子节点后面,键被添加到对应节点分隔键列表的适当位置。如果是B树键值的更新,使用查找算法找到目标叶节点,并将新值与现有键关联即可。

如果目标位置没有足够的空间存储新键值,为了存储更多键值我们必须要拆分该节点成两个节点。拆分节点可能有下面两种情况:

  • 分隔叶子节点:如果叶子节点最多能够保存N个键值对,新插入键值对前该叶子节点就已经保存有N个键值对;
  • 分隔非叶子节点:如果节点可以保存N+1个指向子树的指针,新插入一个子树指针前该节点就已经保存了N+1个指针;

拆分节点时,通常我们需要创建一个新的节点,然后从被拆分节点上转移一半的元素到新创建的节点上;添加新创建节点的第一个分隔键和指针都其父节点对应的位置上(通常称这个键被晋级)。新创建的节点和原来的节点称为兄弟节点。

拆分节点的父节点也有可能没有更多的空间保存被晋级的键和新建节点的指针,这时其父节点也需要拆分。这样的拆分操作有可能需要一直传递到根节点。当根节点也达到它的容量上限时,根节点也必须进行拆分。

这种情况下,首先会新创建一个新的根节点,其中保存了用于拆分旧根节点的键;其次为旧的根节点创建一个兄弟节点,以拆分键平分旧根节点的元素,并转移到新创建的兄弟节点上;最后,添加旧根节点和其兄弟节点的指针到新根节点的子树指针列表中。这个过程中,旧的根节点和新为其创建的兄弟节点一起被降级到第二层,树的高度也增加了1。当根节点被拆分或两个节点合并为新的根节点时,树的高度会发生变,而在叶子和内部节点层,B树只会水平扩展。

图2-11展示了将一个新元素11加入到一个已经完全占用的叶节点的过程。其中一半的元素留在老的节点,另一半元素被转移到新创建的节点上。而拆分点的键(13)被作为分隔键保存到父节点对应的位置。

图2-11

图2-12展示了插入元素11前非叶子节点被完全占用的情况。首先一个新的节点被创建, 待分割节点中从N/2+1开始的元素被移入新创建的节点;其次拆分键被晋级到其父节点;最后新建的节点的指针被加入到其父节点的适当位置。

图2-12

因为非叶节点拆分总是从下往上的,所以父节点需要一个额外的指针位置(指向下一层新创建的节点)。如果父节点没有足够的空间,那么它也必须被拆分。

拆分完成后,原来的一个节点变成两个节点,我们必须选择正确的节点来完成插入操作。如果插入的键小于新晋升的分隔键,则插入新元素到被拆分节点;否则,插入新元素到新创建的节点。

总而言之,节点拆分大致分为四步:

  1. 创建一个新的节点;
  2. 从待分割节点上转移一半元素到新创建的节点上;
  3. 把新元素插入到相应的节点上;
  4. 把新分隔键和新建节点的指针加入到被拆分节点的父节点中;

B树节点的合并

在进行删除操作时,首先找到目标叶子节点,然后删除键和与之关联的值。删除后,如果该叶子节点和它相邻兄弟节点保存的键值对过少(低于某个阀值),这时候就需要合并节点。

准确的说,如果以下条件成立,则需要合并两个节点:

  • 合并叶子节点:如果节点能够保存N个键值对,相邻两个兄弟节点保存的键值对数量之和少于或等于N。
  • 合并非叶子节点: 如果节点能够保存N+1个子树指针,相邻两个兄弟节点保存的指针数量之和少于或等于N+1。

图2-13展示了删除元素16后,B树合并两个叶子节点后结构的变化。通常,我们把右边兄弟节点的元素移入左边节点。但是也可以把左边的元素移入右边节点,只要保证键是有序的即可。

图2-13

图2-14展示了删除元素10后,B树合并两个相邻非叶子节点后结构的变化。我们合并两个节点包含的元素到一个节点中,删除另一个多余的节点。在合并非叶节点的过程中,我们还必须从父节点中转移相应的分隔键到合并后子节点的适当位置(即分隔键的降级)。由于合并导致父节点的指针也减少了一个,由此有可能触发父节点的合并操作。和节点拆分一样,节点的合并也可能需要一直传递到根节点。

图2-14

总而言之,节点合并大概也分为三步:

  1. 转移所有右节点的元素到左边节点;
  2. 从父节点中移除右节点的指针(如果是非叶节点则降级分隔符);
  3. 移除右节点;

在B树中,为了减少节点拆分和合并操作的数量,经常会使用一种称为重新平衡的技术(Rebalancing),我们会在后面的文章中再具体讨论这个问题。


深入理解数据库系统(储存引擎概述1)

深入理解数据库系统之存储存引擎2(数据和索引)

深入理解数据库系统之存储存引擎(二叉搜索树)

b树删除节点每次只能删一个吗_深入理解数据库系统之存储存引擎(B树)相关推荐

  1. b树删除节点每次只能删一个吗_面试官,请不要问我B+树了!!

    每当我们执行某个 SQL 发现很慢时,都会下意识地反应是否加了索引,那么大家是否有想过加了索引为啥会使数据查找更快呢,索引的底层一般又是用什么结构存储的呢,相信大家看了标题已经有答案了,没错!B+树! ...

  2. b+树时间复杂度_深入理解数据库系统之存储存引擎(二叉搜索树)

    B树是数据库存储引擎使用的最多的存储结构之一.许多开源数据库系统也都大量使B用树作为存储结构,多年来已经证明它们能够胜任大多数使用场景. 早在1971年鲁道夫·拜尔(Rudolph Bayer)和爱德 ...

  3. 使用嵌套循环,打印四行五列星星矩形(每次只能打印一个*)

    使用嵌套循环,打印四行五列星星矩形(每次只能打印一个*) ***** ***** ***** ***** 提示:打印不换行,使用 print 方法,不要加 ln 分析: 把*****看成一个整体 字符 ...

  4. 【操作系统】某寺庙,住着一个老和尚和若干小和尚,有一个水缸,由小和尚提水入缸供老和尚饮用。水缸可以容纳10桶水,水取自同一口井中,由于水井口窄,每次只能容纳一个水桶取水,水桶总数为3个。每次往水缸中倒

    题目 某寺庙,住着一个老和尚和若干小和尚,有一个水缸,由小和尚提水入缸供老和尚饮用.水缸可以容纳10桶水,水取自同一口井中,由于水井口窄,每次只能容纳一个水桶取水,水桶总数为3个.每次往水缸中倒水与从 ...

  5. 某寺庙,有小和尚、老和尚若干。有一水缸,由小和尚用水桶从井中提水入缸,老和尚用水桶从缸里取水饮用。水缸可容30桶水,水取自同一井中。水井径窄,每次只能容一个水桶取水。水桶总数为5个。每次入、取缸水仅为

    和尚取水问题 某寺庙,有小和尚.老和尚若干.有一水缸,由小和尚用水桶从井中提水入缸,老和尚用水桶从缸里取水饮用.水缸可容30桶水,水取自同一井中.水井径窄,每次只能容一个水桶取水.水桶总数为5个.每次 ...

  6. 给定一个由n个圆盘组成的塔,这些圆盘按照大小递减的方式套在第一根桩柱上。现要将整个塔移动到另一根桩柱上,每次只能移动一个圆盘,且较大的圆盘在移动过程中不能放置在较小的圆盘上面

    对汉诺塔问题详解,给定一个由n个圆盘组成的塔,这些圆盘按照大小递减的方式套在第一根桩柱上.现要将整个塔移动到另一根桩柱上,每次只能移动一个圆盘,且较大的圆盘在移动过程中不能放置在较小的圆盘上面: 输入 ...

  7. 一、汉诺塔问题 汉诺塔是由三根杆子A,B,C组成的。A杆上有n个(n>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:每次只能移动一个圆盘;大盘不能叠在小盘上面。提示:可将圆

    一.汉诺塔问题 汉诺塔是由三根杆子A,B,C组成的.A杆上有n个(n>1)穿孔圆盘,盘的尺寸由下到上依次变小.要求按下列规则将所有圆盘移至C杆:每次只能移动一个圆盘:大盘不能叠在小盘上面.提示: ...

  8. 打印机顺序打印问题(一台打印机有若干个任务,但是打印机每次只能打印一个任务,每个任务是有优先级的,从1到9)

    一台打印机有若干个任务,但是打印机每次只能打印一个任务,每个任务是有优先级的,从1到9.打印的时候从序列的第一个开始,如果第一个的 优先级不是最大的,则将其出队,并加入队尾.若其优先级是最大的,则直接 ...

  9. 51单片机主函数实现P1口120ms间隔从P1.7口向右循环进行流水灯每次只能亮一个,进入外部中断是进行120ms的延时灯的亮灭各一次

    51单片机主函数实现P1口120ms间隔从P1.7口向右循环进行流水灯每次只能亮一个,进入外部中断是进行120ms的延时灯的亮灭各一次 #include <reg51.h> #includ ...

最新文章

  1. struts2的OGNL表达式(三)
  2. SAP ABAP程序效率优化 Perfomance Tune
  3. Python3 编程示例:斐波纳契数列
  4. 避免jquery的click多次绑定方法
  5. 典型案例道出“服务台”的价值
  6. hdu3068马拉车
  7. git@github.com: Permission denied (publickey).
  8. entity framework6 edmx文件详解
  9. vue/父子组件之间的通信
  10. 实用分享-学习资料下载器
  11. ASP.NET - 一般处理程序获取session值
  12. php对照表,编码对照表的使用(1)-PHP教程,PHP应用
  13. MTK Camera HAL到驱动的流程总结一
  14. 智能点餐mysql框架图_SpringBoot微信点餐系统--P3数据库设计
  15. 虚拟化kvm-虚拟化概述
  16. 物理挖洞之分块 !Cocos Creator !
  17. OpenCV实战之人脸美颜美型(四)——肤色检测
  18. C++ CoreDump
  19. package.json文件解读
  20. 2021年施工员-设备方向-岗位技能(施工员)考试试卷及施工员-设备方向-岗位技能(施工员)证考试

热门文章

  1. Flink 能够改写成 FlinkSQL的理论依据:命令式代码 vs 声明式代码
  2. leetcode 658. Find K Closest Elements | 658. 找到 K 个最接近的元素(二分查找+双指针)
  3. leetcode 415. 字符串相加(Java版)
  4. ERC20 Short Address Attack
  5. 电脑音响怎么插_厦门靓车港丰田凯美瑞汽车音响改装德国HELIX汽车音响|凯美瑞|音响改装|汽车|内饰...
  6. C++学习笔记:(六)public、protected、private继承详解
  7. JavaScript的运动 —— 缓冲运动及其应用篇
  8. C语言:L1-035 情人节 (15分)(解题报告)
  9. linux-磁盘分区、挂载
  10. python笔记之if语句及嵌套浅析