在说B树之前最好先看看2-3树, 2-3树是B树的一种特例, 什么B树, B树就是2-3树, 2-3-4 树 , 2-3-4-5... 树的统称, 而B+树又是B树的一种变形

性质:

  • 什么是二节点, 三节点...?

像上图那样,可以有两个子节点的节点叫做二节点, 可以有三个子节点的节点叫做三节点, 并且, 对于二节点来说,可以有节点,也可以,没有节点, 同样对三节点来说,要么有三个节点要么有没节点, 只有这两种情况(不能只有一个节点)

如果一棵树中最大的节点数为m, 我们就称它是m阶的B树

注意点就是B树的所有的子节点全部在同一层, 如果不在同一层了,它肯定不是B树,下文中会讲解调整的技巧

特性:

假设每一个节点能存储的元素的个数为x

​ 对于根节点来说: 根节点能存储的元素的范围是 1<= x <= m-1 (m为阶数)

​ 对于非根节点来说: 他能存储的元素的个数是  ⌈ m/2 ⌉ -1 <= x <= m-1

像下面这样, 如果当前节点有子节点的话, 子节点的个数是当前节点中存储的元素数+1

推论:

如果当前节点是根节点, 那么, 当前节点的子节点的个数为x: 2 <= x <= m

如果当前节点是非根节点, 那么, 当前节点的子节点的个数x :  ⌈ m/2 ⌉ <= x <= m

例子:

比如当m=3时, 也就是说,当前树为3阶B树, 通过上面的公式计算它的非根节点的个数是 ⌈ m/2 ⌉ <= x <= 3 即 [2,3] , 此时这棵树就是我们所说的2-3树

构建与上溢:

假设我们有这样一个数组: [6,10,4,14,5,11,15,3,2,12,1,7,8] 下面通过画图将它转换成一个3阶B树

在转化的过程中, 严格遵循上面的特性:

先添加node6, 对于二三树来说,最多的节点数是三节点的状态, 于是我们继续添加node10, 得到的下图

因为node4比node6小, 所以我们想把node4添加到node6的左边, 但是此时4,6,10, 三个节点排放在一起就是4节点,不满足2-3的要求,所以我们需要进行上溢调整 , (或者说,当一个节点中存储的元素的个数=m时, 进行上溢处理)

第一步: 先求出需要上溢的节点的中间元素的位置 k (就是下面node6的位置2)

第二步: 将k位置的元素向上移动和父节点合并, 没有父节点,就让他当父节点

第三步: 将[0-k-1] 和 [k-1,m-1] 位置的元素分裂成上溢节点的左右节点

每次上到父节点时,就会导致树长高, 还需要注意的地方就是上溢可能会导致当前节点的父节点满了, 这时候重复这个过程,对其父节点进行上溢的操作

继续添加node5,和node14, 同样可以直接添加进去,且不会打破2-3的平衡

继续添加node11, 按照正常的排序,我们会将他排在node10的右边,但是此时同样,一个节点中三个元素,说明这个节点是4节点, 不符合2-3的三节点特性, 因此需要上溢, 同样是选出中间位置的11,然后和它的上一个父节点进行合并, 剩下的node10, node14当成这个node11的左右子节点

继续添加node15, 按照大小,我们将他排放在node14的右边,且满足二三树的要求

继续添加node3, 按照顺序应该将node3放在node4的左边,但是这个节点里面就回显345三个元素, 也就是说这个节点其实是4节点,因此我们将node4上溢, 和它的父节点合并, 然后我们又发现, 上溢到根节点中,根节点就有了三个元素, 因此我们进行继续上溢, 于是树就会长高一层

继续我们插入node2, 按照大小它被放在node3的左边, 同时树也不会被打破平衡

接着我们插入node12, 同样按照顺序它应该被插入在node14的左边, 但是此时,这个节点中又出现了三个元素, 又出现了4节点, 我们重新进行上溢出, 同样是找到 12 14 15 中间的14去和父节点node11进行合并,下图

用和上面相反的过程我们可以插入node1

同样插入s10这个节点, 同样我们会发现7,8,10三个元素在同一个节点上, 因此我们需要上溢, 然后8,11,14又在同一个节点上, 继续进行上溢, 然后得到下图

删除与下溢

删除一个节点:

  • 如果你想删除的节点在子节点上, 那么就直接进行删除
  • 如果你想删除的节点在根节点行, 就找到这个根节点中这个元素的前驱或者后继,替换根节点中这个元素的位置,然后删除这个用来替换的叶子节点

比如我们想删除60这个节点

于是我们找到node60的前驱节点, 怎么找呢? 从node60开始,往左走一次, 然后往右走到头

如果我们想找到node60的后继节点怎么找呢? 从node60开始,往右走一步,然后往左走到头

  • 实际上每次被删除的节点都是叶子节点
  • 每次添加进来的元素,也必定在叶子节点上

underflow下溢

上溢的话,就是节点会往上走,下溢正好是相反的过程, 比如说,上树m=5, 即5阶的B树, 我们想删除上面的node22, 但是node22被删除后,就只剩下了node24, 这时候就违背了B树的定义, 每一个节点中至少要有 ⌈ m/2 ⌉ -1个元素,即 ⌈ 5/2 ⌉ -1 = 2 个元素

下溢节点中元素的数量必定是 ⌈ 5/2 ⌉ -2

  • 情况1: 向兄弟节点借元素

我们在删除了根节点的右子树中的元素,直接导致了这个节点中的元素个数为 ⌈ m/2 ⌉ -2 , 但是B树中要求的是至少为 ⌈ m/2 ⌉ -1 , 我们就向它的兄弟节点借元素, 因为它的兄弟节点的元素个数比 最低要求还大1, 于是向下面这样变动,让根节点中间的元素,放到根节点右子节点的最左边, 根节点左节点的最右侧的元素放到根节点的中间位置, (注意为了不打破树的平衡,我们需要讲nodeT也倒换过去)

  • 情况2: 兄弟节点不够 ⌈ m/2 ⌉ -1

进行元素的合并, 像下面这样,需要注意的地方就是此时父节点的元素中个数可能不足够 ⌈ m/2 ⌉ -1,因此我们重复这个过程

B树的特征

B树又称为多路搜索树, 是为不同的存储设备设计的平衡查找树,

前面一片博文中我们知道, AVL树确实能中和链式存储结构和顺序存储结果的优缺点, 在添加和删除方面都能表现出优越的性能,这里我们 可以将B树理解成是平衡二叉树的升级版

B树不一定只有两个叉, 但是B树和二叉平衡树一样都是有序的, 即当我们按照中序遍历它树, 可以得到有序数列, 而且B树比AVL树更胖,更矮, ,因此树越高, IO次数就会越大,但是也带来了更多的优势, 每次在磁盘上读取数据时,都会进行一次预读, 使用B树可以很好的实现这个特性

AVL树的磁盘IO性能

假设使用AVL树建立索引, 如上图, 索引文件不会被保存在内存中, (避免索引文件过大,导致内存的溢出), 于是我们如果想根据索引读取数据, 瓶颈就在每次读取索引文件和磁盘之间的IO上

假设我们有上面的树, 然后我们查询node10, 会和磁盘进行如下几次IO

第一次与磁盘的IO : 读取node4, 往右走

第二次与磁盘的IO : 读取node8, 往右走

第三次与磁盘的IO : 读取node9, 往右走

第四次与磁盘的IO : 定位 node10, 结束

虽然使用AVL树建立索引并不是最好的选择, 但是和全表扫描,进行10次IO相比, 效率还是很明显的高效

B树的磁盘IO性能

同样我们想查询node10

第一次与磁盘的IO : 读取node4, 往右走

第二次与磁盘的IO : 读取node6,node8, 往右走

第三次与磁盘的IO : 读取node9,定位到node10

很明显,B树会比AVl树更矮, 当存储的数据超级庞大时, 它甚至可以比AVL树减少一半的IO次数

同样, B树的范围查询效率比较低

B+ 树

概念: 在上树中, 只有最后一行叫做非叶子节点, 其他的都叫做叶子节点

B+树,是B树的一种变体,查询性能更好。

m阶的B+树的特征:

  1. B+树的非叶子节点只用来存储索引信息,而不存储数据,(B树中每一个节点中会存储数据), 这样的会B+树就会在内存中建立庞大的索引体系, 使得他的检索速度超快
  2. 所有项链的叶子节点使用链表这种结构进行结合, 有先后的顺序, 从而使得它的范围查询效率很高

B+树相比于B树的查询优势:

  1. B+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更“矮胖”;
  2. B+树查询必须查找到叶子节点,B树只要匹配到即可不用管元素位置,因此B+树查找更稳定(并不慢);
  3. 对于范围查找来说,B+树只需遍历叶子节点链表即可,b树却需要重复地中序遍历

缺点: 会用冗余的节点, 比较占硬盘的大小, (但是这些冗余的节点都是索引,会大大提高查询速度)

心里有点B树的更多相关文章

  1. B树——算法导论(25)

    B树 1. 简介 在之前我们学习了红黑树,今天再学习一种树--B树.它与红黑树有许多类似的地方,比如都是平衡搜索树,但它们在功能和结构上却有较大的差别. 从功能上看,B树是为磁盘或其他存储设备设计的, ...

  2. ASP.NET Aries 入门开发教程8:树型列表及自定义右键菜单

    前言: 前面几篇重点都在讲普通列表的相关操作. 本篇主要讲树型列表的操作. 框架在设计时,已经把树型列表和普通列表全面统一了操作,用法几乎是一致的. 下面介绍一些差距化的内容: 1:树型列表绑定: v ...

  3. 再讲IQueryable<T>,揭开表达式树的神秘面纱

    接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...

  4. HDU1671——前缀树的一点感触

    题目http://acm.hdu.edu.cn/showproblem.php?pid=1671 题目本身不难,一棵前缀树OK,但是前两次提交都没有成功. 第一次Memory Limit Exceed ...

  5. 算法与数据结构(十一) 平衡二叉树(AVL树)

    今天的博客是在上一篇博客的基础上进行的延伸.上一篇博客我们主要聊了二叉排序树,详情请戳<二叉排序树的查找.插入与删除>.本篇博客我们就在二叉排序树的基础上来聊聊平衡二叉树,也叫AVL树,A ...

  6. [C#] C# 知识回顾 - 表达式树 Expression Trees

    C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...

  7. bzoj3207--Hash+主席树

    题目大意: 给定一个n个数的序列和m个询问(n,m<=100000)和k,每个询问包含k+2个数字:l,r,b[1],b[2]...b[k],要求输出b[1]~b[k]在[l,r]中是否出现. ...

  8. bzoj1901--树状数组套主席树

    树状数组套主席树模板题... 题目大意: 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]--a[ ...

  9. bzoj3932--可持久化线段树

    题目大意: 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第 ...

  10. jquery-treegrid树状表格的使用(.Net平台)

    上一篇介绍了DataTable,这一篇在DT的基础之上再使用jquery的一款插件:treegrid,官网地址:http://maxazan.github.io/jquery-treegrid/ 一. ...

随机推荐

  1. [POJ3523]The Morning after Halloween

    Description You are working for an amusement park as an operator of an obakeyashiki, or a haunted ho ...

  2. opencv::卷积运算函数filter2D()

    opencv::卷积运算函数filter2D() 使用掩模板矩阵(kernel)计算每个像素值 与原图相比,没有黑边 int main(int argc, char** argv) { Mat src ...

  3. boost::asio::tcp

    同步TCP通信服务端 #include <boost/asio.hpp> #include <iostream> using namespace boost::asio; in ...

  4. POJ 3259 Wormholes(Bellman-Ford)

    题目网址:http://poj.org/problem?id=3259 题目: Wormholes Time Limit: 2000MS   Memory Limit: 65536K Total Su ...

  5. 玩转 RTC时钟库 DS3231

    1.前言     接着博主的上一篇 玩转 RTC时钟库 + DS1302,这一篇我们重点讲解DS3231时钟模块.没有看过上一篇的同学,麻烦先去阅读一下,因为很多理论基础已经在上一篇做了详细讲解,这里 ...

  6. HTML CSS整理笔记

    ——修改placeholder提示的样式: 1.除IE外通用写法 类名或标签名::placeholder {color: red;}2.加兼容前缀写法 css超出一行显示省略号:给定宽度(width: ...

  7. SpringCloud之整合Zipkin+Sleuth(十四)

    1.添加依赖 在项目的pom.xml文件中添加下面依赖 <!--里面包含两个依赖--> <dependency> <groupId>org.springframew ...

  8. C和C++中的引用传递

    两种引用传递的定义方式 第一种 #include<stdio.h> void changeValue(int *a); int main(){ int a =1; changeValue( ...

  9. 以Mnist为例从头开始自己建立数据集,搭建resnet34,识别Mnist

    写在前面: 本人小白研一,刚开始学习深度学习,将自己的第一个实验过程总结下来,看了很多的大牛的博客,在下面的程序中也参考了很多大牛的博客.在刚开始入门的学习的时候,直接编写程序下载数据集,但是后来觉得 ...

  10. 第三十五章 POSIX共享内存

    POSIX共享内存函数介绍 shm_open 功能: 用来创建或打开一个共享内存对象 原型: int shm_open(const char *name, int oflag, mode_t mode ...