目录
- 前言-为什么要使用B-树?
- B-树概念
前言-为什么要使用B-树?
首先,我们正常的搜索都有一下方式:
- 搜索二叉树,极端场景下会退化,类似于单支,此时的效率变成了O(N);
- 为了解决1的问题,提出了平衡树的概念,左右子树的高度差不大于1,AVL树,红黑树。该效率为O(logN),其中map/set就是由此构建的;
- 更好的搜索结构则有哈希/散列表,该效率为O(1),–unordered_map/unordered_set
- 跳表、字典树
上面的结构都是完成内存中数据的搜索查找问题
但假设此时的数据量很多,在内存中存放不下,数据要存到磁盘中,上面的数据结构就不好了,虽然可以把内存在磁盘的地址使用AVL树来存储,查找的时间复杂度为O(logN),但是该复杂度在内存中访问非常快,在磁盘中,logN次磁盘IO访问会非常慢。 如果换成哈希表,变成O(1),在极端情况下,哈希表冲突十分厉害,一个桶中数据太多,会影响效率,并且哈希表中存在很多附带数据(表结构、节点中的指针等),数据量很大时,内存占用很多。B树则能解决这些问题。
B-树概念
B树是一种平衡的多叉树,一颗M阶(M>2)的B树,为平衡的M路平衡搜索树,可以是空树或者满足以下性质:
- 根节点至少有两个孩子
- 每个非根节点至少有M/2(向上取整)个孩子,至多有M个孩子
- 每个非根节点至少有M/2-1(向上取整)个关键字,至多有M-1个关键字,并且以升序排列
- key(1)和key(i+1)之间的孩子节点的值介于key[i]、key[i+1]之间
- 所有的叶子节点都在同一层
对上述性质进行总结来说:
根节点:关键字数量[1,M-1],孩子数量[2,M]
非根节点:关键字数量[M/2-1, M-1],孩子数量[M/2,M]
每个节点中,孩子的数量比关键字的数量永远要多一个
那么为什么会有这样的性质呢?结合例子来进行理解
针对根节点的数量范围分析:
首先,一个关键字会有两个孩子(左孩子和右孩子),其中和相邻的关键字会共有一个孩子,即关键字1的右孩子也是关键字2的左孩子,那么孩子的数量就会比关键字的数量多一个。
针对非根节点的数量范围分析:
假设M等于3,那么根节点的关键字数量最多只能放2个,如果放到了3个,则违反了规则,根节点最多存M-1个关键字,那么就会进行分裂,创建一个兄弟节点,右边M/2的值拷贝到兄弟节点中,中间值插入到父亲,如果没有父亲,则创建新的父亲,该值作为新的根。也就是上图右下角的节点,关键字70超出范围,则进行分裂,将70分裂为兄弟节点,50插入到父亲节点。
那么为什么分裂的时候要提中位数插入到父亲呢?
因为分裂新增一个兄弟节点,对于父亲而言,多了一个孩子,还得多一个关键字,这样才能保持孩子的数量比关键字数量多一个。
结合分裂的思想:
如果M是奇数,分裂时两边数量为M/2,中间值插入到父亲。(比如M=9,左右各为4,剩余的一个节点插入到父亲,如果没有父亲则创建)
如果M是偶数,因为两边要有一个需要插入到父亲,因此总有一边要少一个,一边是M/2,一边是M/2-1。(比如M=10,左右为5和4或者4和5,剩余一个插入到父亲,如果没有父亲则创建)