B树与B+树
B树
如果前面的2-3树与2-3-4树理解了,B树也就理解了,因为2-3树就是3阶的B树,2-3-4树就是4阶的B树。所以,对于B树的性质,根据2-3-4树都可以推导出来了,即,
一颗m阶的B树(B-tree) 定义如下:
(1)每个节点最多有 m-1 个key;
(2)根节点至少有1个key;
(3)非根节点至少有 Math.ceil(m/2)-1 个key;
(4)每个节点中的key都按照从小到大的顺序排列,每个key的左子树中的所有key都小于它,而右子树中的所有key都大于它;
(5)所有叶子节点都位于同一层,即根节点到每个叶子节点的长度都相同。
如下图是一棵4阶B树(2-3-4树),
在前面的章节我就已经说过,出现多路查找树的原因,是因为多路查找树的数据结构,用在内存读取外存的场景下,可以减少磁盘的IO次数,因为在高阶的情况下,树不用很高就可以标识很大的数据量了,那这个怎么算的呢?
打个比方,以2-3树为例,树高为3的时候,一棵2-3树可以保存2+3x2+3x2x2=20个key,若当B树的阶数达到1001阶,即一个节点可以放1000个key,然后树高还是3,即 1000+1000x1001+1000x1001x1000 ,零头不算了,即至少可以放10个亿的key,此时我们只要让根节点读取到内存中,把子节点及子孙节点持久化到硬盘中,那么在这棵树上,寻找某一个key至多需要2次硬盘的读取即可。
而对于B树节点的插入,可以类比2-3-4树,即,若节点插入节点的key还未“丰满”,则直接插入,若节点插入节点的key已“丰满”,则插入节点之后分裂,再以分裂之后的父节点看作向上层插入的节点调整,直至满足该 m 阶的B树。如下,为5阶B树插入节点的动态图,
注:5阶的非根节点至少有 Math.ceil(5/2)-1 个key,即,至少要有2个key,根节点可以为1个key
对于B树节点的删除,也一样类比2-3-4树,如下,
(1)若删除非叶子节点,找后继节点替换之,将问题转化为删除叶子节点;
(2)若删除叶子节点,且叶子节点的key数大于定义中的最小值(根节点至少有1个key,非根节点至少有 Math.ceil(m/2)-1 个key),则直接删除即可,无需调整,
(3)若删除叶子节点,且叶子节点的key数刚好满足定义中的最小值,即刚好“脱贫”,则将节点删除,此时树肯定需要调整,即:
a.若删除节点的相邻兄弟节点的key数“富裕”(节点的key大于定义中的最小值),则父节点的1个key下移与待删除的节点合并,相邻兄弟节点的1个key上移与父节点合并,完成调整;
b.若删除节点的相邻兄弟节点的key数刚好“脱贫”(节点的key刚好满足定义的最小值),则父节点的1个key下移与待删除的节点及相邻兄弟节点,三者进行合并成一个节点,若下移1个key后的父节点的key数刚好“脱贫”或“富裕”,则调整完成,反之,即此时父节点已经陷入“贫穷”,则将父节点看作当前待删除的节点,重复a,b的判断。
如下,是5阶B树删除节点的动态图,
B+树
虽然B树这种数据结构,应用在内外存交互,可以极大的减少磁盘的IO次数,但还是有些小瑕疵,如下5阶的B树图,若我需要读取key为“66”与“73”的数据,则此时从根节点“50”开始,“66”大于“50”,找右孩子,即到“60 70 120”的节点,再锁定到“64 66”的节点,找到key为“66”的数据,然后读“73”的数据,再重新从根开始往下寻找key为“73”的数据,如果需要查询的数据量一多,性能就很糟糕。还有一点,就是B树的每个节点都包含key及其value数据,这样的话,我每次读取叶子节点的数据时,在经过路径上的非叶子节点也会被读出,但实际上这部分数据我是不需要的,这样又占用了没有必要的内存空间。
所以,B+树在B树的基础上做了优化,它与B树的差异在于:
(1)有 k 个子节点的节点必然有 k 个key;
(2)非叶子节点仅具有索引作用,跟记录有关的信息均存放在叶子节点中。
(3)树的所有叶子节点构成一个有序链表,可以按照key排序的次序遍历全部记录。
即,B和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。
B+树的优点在于:
1.由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。
数据存放的更加紧密,具有更好的空间局部性。
因此访问叶子节点上关联的数据也具有更好的缓存命中率。
2.B+树的叶子结点都是相链的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。
而且由于数据顺序排列并且相连,所以便于区间查找和搜索。
而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
但是B树也有优点,其优点在于:
由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。
另外B/B+树也经常用做数据库的索引,推荐看张洋的 MySQL索引背后的数据结构及算法原理 这篇文章,这篇文章对MySQL中的如何使用B+树进行索引有比较详细的介绍,推荐阅读。
上一篇: Java实训项目:GUI学生信息管理系统