前言
今天我们再来学习另外一种高级数据结构B树,我们知道树的查询时间复杂度和其树的高度有直接关系,当我们向红黑树里面插入大量的数据时,有两个问题:
(1)首先,内存是有限的不可能无止境的一直插入数据,而基于BST的平衡树如AVL树和红黑树,本质上都是基于内存的数据结构。
(2)如果将AVL树,红黑树或者跳跃表直接存储到磁盘上,然后用来检索可以吗? 答案是可以的,但问题是基于磁盘寻道查询比基于内存慢太多了,举个例子,假设现在有100万数据分布在红黑树里面,按照红黑树的平均查找性能O(logN)来计算,在N=100万时,检索任意数据平均需要20次查询,如果是在内存完全没问题,但此时红黑树是如果存在磁盘上,那就完全不一样了,按照普通硬盘或者文件系统,每次查询IO一次总耗时约10毫秒计算,那么在百万级别的数据量下,检索一次数据就需要10*20,大概就是0.2秒,假如现在数据量扩大100倍,数据总量是一亿,那么检索一次数据就需要20秒,这个性能是完全不能接受的,可以想象在这种情况下,使用二叉树是不合适的。
正是由于描述的问题,所以迫切需要一种面向磁盘或者文件系统友好的数据结构,而它就是B树,或者基于B树的扩展B+,B*树等。上面问题的根源在于树的高度太高,导致查询外存,也就是访问磁盘IO次数太多,从而大大拖慢了检索性能,那么思路就清晰了,只要想法子降低树的高度就可以了,没错B树就是这么一种m叉的多路平衡树,你可以想象它的孩子节点少则2个,多则数千个,所以就从二叉树的廋高,变成了多叉树的矮胖,正是这样才大大降低了树的高度,从而使得这种数据结构更适合存储在磁盘上,并且充分了利用了磁盘的块的预读机制(局部性访问原理),通过缓存的方式进一步提升性能,磁盘在读取某一个文件指针的时候,通常会把紧挨着的数据,也全部读到缓存,因为实践证明,当一个文件数据被访问的时候,它周边的数据很快也会被访问,这里简单说下磁盘扇区,磁盘块,磁盘也的区别:
扇区:磁盘的最小存储单位;
磁盘块:文件系统读写数据的最小单位;
页:内存的最小存储单位;
磁盘块和页的大小一般情况下为4KB,所以在B树中一个节点的最大存储数据个数的总大小一般不能超过4KB。
基于B树的结构充分利用了这一点,从而非常适合做文件系统或者数据库的索引。
B-树的性质
一颗M阶B树T,满足以下条件
1. 每个结点至多拥有M棵子树
2. 根结点至少拥有两棵子树
3. 除了根结点以外,其余每个分支结点至少拥有M/2棵子树
4. 所有的叶结点都在同一层上
5. 有k棵子树的分支结点则存在k-1个关键字,关键字按照递增顺序进行排序
6. 关键字数量满足ceil(M/2)-1 <= n <= M-1
下面咱们以一棵5阶(M=5,即除根结点和叶子结点之外的内结点最多5个孩子,最少3个孩子)B树实例进行讲。
插入(insert)操作
- 插入一个元素时,首先在B树中是否存在,如果不存在,即在叶子结点处结束,然后在叶子结点中插入该新的元素,注意:
- 如果叶子结点空间足够,这里需要向右移动该叶子结点中大于新插入关键字的元素,如果空间满了以致没有足够的空间去添加新的元素,则将该结点进行“分裂”,将一半数量的关键字元素分裂到新的其相邻右结点中,中间关键字元素上移到父结点中(当然,如果父结点空间满了,也同样需要“分裂”操作),而且当结点中关键元素向右移动了,相关的指针也需要向右移。
- 如果在根结点插入新元素,空间满了,则进行分裂操作,这样原来的根结点中的中间关键字元素向上移动到新的根结点中,因此导致树的高度增加一层。