一、背景
1、分级存储
现代电子计算机发展速度空前,然而从实际应用的需求来看,问题规模的膨胀却远远快于存储能力的增长。以数据库为例,在20世纪80年代初,典型数据库的规模为10~100MB,而三十年后,典型数据库的规模已需要以TB为单位来计量。实践证明,分级存储才是行之有效的方法。在由内存与外存(磁盘)组成的二级存储系统中,数据全集往往存放于外存中,计算过程中则可将内存作为外村的高速缓存,存放最常用数据项的副本。借助高效的调度算法,如此便可将内存的“高速度”与外存的“大容量”结合起来。两个相邻存储级别之间的数据传输,统称I/O操作。各级存储器的访问速度相差悬殊,故应尽可能地减少I/O操作。也正因为此,在衡量相关算法的性能时,基本可以忽略对内存的访问,转而更多地关注对外存的访问次数。
2、多路搜索树
当数据规模大到内存已不足以容纳时,常规平衡二叉搜索树的效率将大打折扣。因为查找过程对外存的访问次数过多。例如,若将10^9个记录在外存中组织为AVL树,则每次查找大致需做30次外存访问。为此,需要充分利用磁盘之类外部存储器的另一特性:就时间成本而言,读取物理地址连续的一千个字节,与读取单个字节几乎没有区别。既然外部存储器更适宜于批量式访问,不妨通过时间成本相对极低的多次内存操作,来代替时间成本相对极高的单次外存操作。相应,需要将通常的二叉搜索树改造为多路搜索树----在中序遍历意义下也是一种等价变换。
如图1.1所示,比如可以两层为间隔,将各节点与其左、右孩子合并为“大节点”,改造后的每个“大节点“拥有四个分支,故称作四路搜索树。这一策略还可进一步推广,比如以三层为间隔合并,进而得到八路搜索树。一般地,以k层为间隔如此重组,可将二叉搜索树转化为等价的2^k路搜索树,统称多路搜索树。不难验证,多路搜索树同样支持查找等操作,且效果与原二叉搜索树完全等同;然而重要的是,其对外存的访问方式已发生本质变化。实际上,在此时的搜索每下降一层,都以”大节点“为单位从外存读取一组(而不再是单个)关键码。更为重要的是,这组关键码在逻辑上与物理上都彼此相邻,故可以批量方式从外存一次性读出,且所需时间与读取单个关键码几乎一样。每组关键码的最佳数目,取决于不同外存的批量访问特性。比如旋转式磁盘的读写操作多以扇区为单位,故可根据扇区的容量和关键码的大小,经换算得出每组关键码的最佳规模。
二、概念
B树英文原文为B-tree,也译为B-树。所谓m阶B树,即为m路平衡搜索树(m>=2),其宏观结构如图2.1所示。
-
所有外部节点均深度相等。
-
每个内部节点都存有不超过m-1个关键码,以及用以指示对应分支的不超过m个引用。具体地,存有n<=m-1个关键码:K1<K2<K3<K4< ...<Kn的内部节点,同时还配有n+1<=m个引用:A0<A1<A2<A3<A4<...<An。(也即每个节点至多有m棵子树。)
-
在非空B树中,根节点应满足:n+1 >= 2。(即非空B树根节点至少有两棵子树。)
-
除根节点以外的所有内部节点,都应满足:n+1 >= Γm/2˥。(即除根节点以外的所有内部节点至少有Γm/2˥棵子树。)
由于各节点的分支数介于Γm/2˥至m之间,故m阶B树也称作(Γm/2˥,m)树,如(2,3)树、(3,6)树等。
B树的外部节点实际上未必意味着查找失败,而可能表示目标关键码存在于更低层次的某一外部存储系统中,顺着该节点的指示,即可深入至下一级存储系统并继续查找。如图2.2即为一棵由9个内部节点、15个外部节点以及14个关键码组成的4阶B树,其高度h=3,其中每个节点包含1~3个关键码,拥有2~4个分支。
三、实现
1、B树节点
#define BTNodePosi(T) BTNode<T>* //B-树节点位置
template<typename T>
struct BTNode { //B-树节点模板类
//成员
BTNodePosi(T) parent; //父节点
vector<T> key; //关键码向量
vector<BTNodePosi(T)> child; //孩子向量(其长度总比key多一)
//构造函数(注意:BT