B+树

B+树是B树的一种变体,也属于平衡多路查找树,大体结构与B树相同,包含根节点、内部节点和叶子节点。多用于数据库和操作系统的文件系统中,由于B+树内部节点不保存数据,所以能在内存中存放更多索引,增加缓存命中率。另外因为叶子节点相连遍历操作很方便,而且数据也具有顺序性,便于区间查找。

B+树特点

  • B+树可以定义一个m值作为预定范围,即m路(阶)B+树。
  • 根节点可能是叶子节点,也可能是包含两个或两个以上子节点的节点。
  • 内部节点如果拥有k个关键字则有k+1个子节点。
  • 非叶子节点不保存数据,只保存关键字用作索引,所有数据都保存在叶子节点中。
  • 非叶子节点有若干子树指针,如果非叶子节点关键字为k1,k2,...kn,其中n=m-1,那么第一个子树关键字判断条件为小于k1,第二个为大于等于k1而小于k2,以此类推,最后一个为大于等于kn,总共可以划分出m个区间,即可以有m个分支。(判断条件其实没有严格的要求,只要能实现对B+树的数据进行定位划分即可,有些实现使用了m个关键字来划分区间,也是可以的)
  • 所有叶子节点通过指针链相连,且叶子节点本身按关键字的大小从小到大顺序排列。
  • 自然插入而不进行删除操作时,叶子节点项的个数范围为[floor(m/2),m-1],内部节点项的个数范围为[ceil(m/2)-1,m-1]。
  • 另外通常B+树有两个头指针,一个指向根节点一个指向关键字最小的叶子节点。
  • 在进行删除操作时,涉及到索引节点填充因子和叶子节点填充因子,一般可设叶子节点和索引节点的填充因子都不少于50%。

以下是一棵4阶B+树,

插入操作

假设现在构建一棵四阶B+树,开始插入“A”,直接作为根节点,

插入“B”,大于“A”,放右边,

插入“C”,按顺序排到最后,

继续插入“D”,直接添加的结果如下图,此时超过了节点可以存放容量,对于四阶B+树每个节点最多存放3个项,此时需要执行分裂操作,

分裂操作为,先选取待分裂节点中间位置的项,这里选“C”,然后将“C”项放到父节点中,因为这里还没有父节点,那么直接创建一个新的父节点存放“C”,而原来小于“C”的那些项作为左子树,原来大于等于“C”的那些项作为右子树。这里注意下非叶子节点存放的都是关键字,用作索引的,所以父节点存放的“C”项不包括数据,数据仍然存放在右子树。此外,还需要添加一个指针,由左子树指向右子树。

继续插入“M”,“M”大于“C”,往右子节点,

分别与“C”“D”比较,大于它们,放到最右边,

插入“L”,“L”大于“B”,往右子树,

“L”逐一与节点内项的值比较,根据大小放到指定位置,此时触发分裂操作,

选取待分裂节点中间位置的项“L”,然后将“L”项放到父节点中,按大小顺序将“L”放到指定位置,而原来小于“L”的那些项作为左子树,原来大于等于“L”的那些项作为右子树。父节点存放的“L”项不包括数据,数据仍然存放在右子树。此外,还需要在左子树中添加一个指向右子树的指针。

继续插入“K”,从根节点开始查找,逐一比较关键字,“K”大于“C”而小于“L”,往第二个分支,

在子节点中逐一比较,“K”最终落在最右边,

继续插入“J”,从根节点开始查找,逐一比较关键字,“J”大于“C”而小于“L”,往第二个分支,

在子节点中找到“J”的相应位置,此时超过了节点的容量,需要进行分裂操作,

选取待分裂节点中间位置的项“J”,然后将“J”项放到父节点中,按大小顺序将“J”放到指定位置,而原来小于“J”的那些项作为左子树,原来大于等于“J”的那些项作为右子树。父节点存放的“J”项不包括数据,数据仍然存放在右子树。此外,还需要在左子树中添加一个指向右子树的指针。

继续插入“I”,从根节点开始查找,逐一比较关键字,“I”大于“C”而小于“J”“L”,往第二个分支,

逐一比较找到“I”的插入位置,

继续插入“H”,从根节点开始查找,逐一比较关键字,“H”大于“C”而小于“J”“L”,往第二个分支,

“H”逐一与节点内的值比较,根据大小放到指定位置,此时触发分裂操作,

选取待分裂节点中间位置的项“H”,然后将“H”项放到父节点中,按大小顺序将“H”放到指定位置,而原来小于“H”的那些项作为左子树,原来大于等于“H”的那些项作为右子树。父节点存放的“H”项不包括数据,数据仍然存放在右子树。此外,还需要在左子树中添加一个指向右子树的指针。

但此时父节点超出了容量,父节点需要继续分裂操作,

选取待分裂节点中间位置的项“J”,然后将“J”项放到父节点中,但还不存在父节点,需要创建一个作为父节点。原来小于“J”的那些项作为左子树,原来大于“J”的那些项作为右子树。这是非叶子节点的分裂,操作对象都是用作索引的关键字,不必考虑数据存放问题。

插入“G”,从根节点开始查找,“G”小于“J”,往第一个分支,

逐一比较节点内项的值,“G”大于“C”小于“H”,往第二个分支,

逐一比较节点内项的值,找到“G”的位置并插入,

插入“F”,从根节点开始查找,“F”小于“J”,往第一个分支,

逐一比较节点内项的值,“F”大于“C”小于“H”,往第二个分支,

逐一比较节点内项的值,找到“F”的位置并插入,此时触发分裂操作,

选取待分裂节点中间位置的项“F”,然后将“F”项放到父节点中,按大小顺序将“F”放到指定位置,而原来小于“F”的那些项作为左子树,原来大于等于“F”的那些项作为右子树。父节点存放的“F”项不包括数据,数据仍然存放在右子树。此外,还需要在左子树中添加一个指向右子树的指针。

最后插入“E”,从根节点开始查找,“E”小于“J”,往第一个分支,

逐一比较节点内项的值,“E”大于“C”小于“F”,往第二个分支,

逐一比较节点内项的值,找打“E”适当的位置并插入。

从上面插入操作可以总结,插入主要就是涉及到分裂操作,而且要注意到非节点只保存了关键字作为索引,而数据都保存在叶子节点上,此外还需要使用指针将叶子节点连接起来。最终我们可以看到叶子节点的项按从小到大排列,因为有了指针使得可以很方便遍历数据。

查找操作

对B+树的查找与B树的查找差不多,从根节点开始查找,通过比较项的值找到对应的分支,然后继续往子树上查找。

比如查找“H”,“H”小于“J”,往第一个分支,

逐一比较节点中的项,发现应该往第四个分支,

逐一比较,找到“H”。

遍历操作

遍历操作首先是要先找到树最左边的叶子节点,然后就可以通过指针完成整棵树的遍历了。

从根节点开始,一直往第一个分支走,

继续往第一个分支走,

发现已经到叶子节点了,这就是要找的遍历的开端,

第一个叶子节点有两个项,接着根据指针跳到第二个叶子节点,

第二个节点有三个项,根据指针继续往下一个节点,

该节点有两个项,根据指针继续往下一个节点,

不断根据指针往下,

往下,

完成整棵树的遍历。

作者:超人汪小建
链接:https://juejin.im/post/5b9073f9f265da0acd209624
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

10-看图理解数据结构与算法系列(B+树)的更多相关文章

  1. 09-看图理解数据结构与算法系列(B树)

    B树 B树即平衡查找树,一般理解为平衡多路查找树,也称为B-树.B_树.是一种自平衡树状数据结构,能对存储的数据进行O(log n)的时间复杂度进行查找.插入和删除.B树一般较多用在存储系统上,比如数 ...

  2. 06-看图理解数据结构与算法系列(AVL树)

    AVL树 AVL树,也称平衡二叉搜索树,AVL是其发明者姓名简写.AVL树属于树的一种,而且它也是一棵二叉搜索树,不同的是他通过一定机制能保证二叉搜索树的平衡,平衡的二叉搜索树的查询效率更高. AVL ...

  3. 19-看图理解数据结构与算法系列(Radix树)

    Radix树 Radix树,即基数树,也称压缩前缀树,是一种提供key-value存储查找的数据结构.与Trie不同的是,它对Trie树进行了空间优化,只有一个子节点的中间节点将被压缩.同样的,Rad ...

  4. 13-看图理解数据结构与算法系列(Trie树)

    Trie树 Trie树,是一种搜索树,也称字典树或单词查找树,此外也称前缀树,因为某节点的后代存在共同的前缀.它的key都为字符串,能做到高效查询和插入,时间复杂度为O(k),k为字符串长度,缺点是如 ...

  5. 11-看图理解数据结构与算法系列(B树的删除)

    删除操作 删除操作比较复杂,主要是因为删除的项可能在叶子节点上也可能在非叶子节点上,而且删除后可能导致不符合B树的规定,这里暂且称之为导致B树不平衡,于是要进行一些合并.左旋.右旋等操作,使之符合B树 ...

  6. 17-看图理解数据结构与算法系列(NoSQL存储-LSM树)

    关于LSM树 LSM树,即日志结构合并树(Log-Structured Merge-Tree).其实它并不属于一个具体的数据结构,它更多是一种数据结构的设计思想.大多NoSQL数据库核心思想都是基于L ...

  7. 看图轻松理解数据结构与算法系列(NoSQL存储-LSM树) - 全文

    <看图轻松理解数据结构和算法>,主要使用图片来描述常见的数据结构和算法,轻松阅读并理解掌握.本系列包括各种堆.各种队列.各种列表.各种树.各种图.各种排序等等几十篇的样子. 关于LSM树 ...

  8. javascript实现数据结构与算法系列:栈 -- 顺序存储表示和链式表示及示例

    栈(Stack)是限定仅在表尾进行插入或删除操作的线性表.表尾为栈顶(top),表头为栈底(bottom),不含元素的空表为空栈. 栈又称为后进先出(last in first out)的线性表. 堆 ...

  9. 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解

    数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...

随机推荐

  1. Luogu P1462 通往奥格瑞玛的道路【二分/最短路】

    题目背景 在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量 有一天他醒来后发现自己居然到了联盟的主城暴风城 在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛 题目描述 在艾泽拉斯, ...

  2. flask框架基础入门一

    首先:flask是一个基于Werkzeug,Jinja2的一个python的微服务框架. 安装flask框架: pip install flask 一个最小的最简单的flask应用: from fla ...

  3. Hdu 3289 Rain on your Parade (二分图匹配 Hopcroft-Karp)

    题目链接: Hdu 3289 Rain on your Parade 题目描述: 有n个客人,m把雨伞,在t秒之后将会下雨,给出每个客人的坐标和每秒行走的距离,以及雨伞的位置,问t秒后最多有几个客人可 ...

  4. Android 线程池系列教程(4) 启动线程池中的线程和中止池中线程

    Running Code on a Thread Pool Thread 上一课   下一课 1.This lesson teaches you to Run a Runnable on a Thre ...

  5. Spirng MVC +Velocity 表单绑定命令对象

    通常,表单中的数据在提交之后可以通过Spring MVC的@RequestParam注解在控制器函数的参数列表中中提取出来,但是一旦表单数据过多的话,参数列表将会变得非常长,最好的解决方案是将表单中的 ...

  6. [转]Asp.net Mvc2中重构View的三种方式

    本文转自:http://www.cnblogs.com/zhuqil/archive/2010/07/14/asp-net-mvc2-view-refactoring.html 我们在Asp.net ...

  7. Java开发笔记(九十三)深入理解字节缓存

    前面介绍了文件通道的读写操作,其中用到字节缓存ByteBuffer,它是位于通道内部的存储空间,也是通道唯一可用的存储形式.ByteBuffer有两种构建方式,一种是调用静态方法wrap,根据输入的字 ...

  8. lua centos 安装报错

    yum install libtermcap-devel ncurses-devel libevent-devel readline-devel

  9. 用js的eval函数模拟Web API中的onclick事件

    在检查组内小伙伴提交的tabToggler插件的js代码时,发现了onclick的如下用法: el.onclick = function(){ //按钮样式切换 for(var i=0;i<ob ...

  10. 关于flex布局对自己的影响

    对于本图来说用了一个效果就能达到这种情况,对于我来说,今天是有进步的,具体操作就是盒子模型确实,在什么地方起来的flex就运用到该地方去,刚 开始就一直有问题,思考了半天,原来是我的控制代码出现了点错 ...