[TOC]node
在说B树以前最好先看看2-3树, 2-3树是B树的一种特例, 什么B树, B树就是2-3树, 2-3-4 树 , 2-3-4-5... 树的统称, 而B+树又是B树的一种变形数组
像上图那样,能够有两个子节点的节点叫作二节点, 能够有三个子节点的节点叫作三节点, 而且, 对于二节点来讲,能够有节点,也能够,没有节点, 一样对三节点来讲,要么有三个节点要么有没节点, 只有这两种状况(不能只有一个节点)性能
若是一棵树中最大的节点数为m, 咱们就称它是m阶的B树设计
注意点就是B树的全部的子节点所有在同一层, 若是不在同一层了,它确定不是B树,下文中会讲解调整的技巧3d
特性:code
假设每个节点能存储的元素的个数为xblog
对于根节点来讲: 根节点能存储的元素的范围是 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
咱们在删除了根节点的右子树中的元素,直接致使了这个节点中的元素个数为 ⌈ m/2 ⌉ -2 , 可是B树中要求的是至少为 ⌈ m/2 ⌉ -1 , 咱们就向它的兄弟节点借元素, 由于它的兄弟节点的元素个数比 最低要求还大1, 因而向下面这样变更,让根节点中间的元素,放到根节点右子节点的最左边, 根节点左节点的最右侧的元素放到根节点的中间位置, (注意为了避免打破树的平衡,咱们须要讲nodeT也倒换过去)
进行元素的合并, 像下面这样,须要注意的地方就是此时父节点的元素中个数可能不足够 ⌈ m/2 ⌉ -1,所以咱们重复这个过程
B树又称为多路搜索树, 是为不一样的存储设备设计的平衡查找树,
前面一片博文中咱们知道, AVL树确实能中和链式存储结构和顺序存储结果的优缺点, 在添加和删除方面都能表现出优越的性能,这里咱们 能够将B树理解成是平衡二叉树的升级版
B树不必定只有两个叉, 可是B树和二叉平衡树同样都是有序的, 即当咱们按照中序遍历它树, 能够获得有序数列, 并且B树比AVL树更胖,更矮, ,所以树越高, IO次数就会越大,可是也带来了更多的优点, 每次在磁盘上读取数据时,都会进行一次预读, 使用B树能够很好的实现这个特性
假设使用AVL树创建索引, 如上图, 索引文件不会被保存在内存中, (避免索引文件过大,致使内存的溢出), 因而咱们若是想根据索引读取数据, 瓶颈就在每次读取索引文件和磁盘之间的IO上
假设咱们有上面的树, 而后咱们查询node10, 会和磁盘进行以下几回IO
第一次与磁盘的IO : 读取node4, 往右走
第二次与磁盘的IO : 读取node8, 往右走
第三次与磁盘的IO : 读取node9, 往右走
第四次与磁盘的IO : 定位 node10, 结束
虽然使用AVL树创建索引并非最好的选择, 可是和全表扫描,进行10次IO相比, 效率仍是很明显的高效
一样咱们想查询node10
第一次与磁盘的IO : 读取node4, 往右走
第二次与磁盘的IO : 读取node6,node8, 往右走
第三次与磁盘的IO : 读取node9,定位到node10
很明显,B树会比AVl树更矮, 当存储的数据超级庞大时, 它甚至能够比AVL树减小一半的IO次数
一样, B树的范围查询效率比较低
**概念: ** 在上树中, 只有最后一行叫作非叶子节点, 其余的都叫作叶子节点
B+树,是B树的一种变体,查询性能更好。
m阶的B+树的特征:
B+树相比于B树的查询优点:
缺点: 会用冗余的节点, 比较占硬盘的大小, (可是这些冗余的节点都是索引,会大大提升查询速度)