详细内容,参见July博文:从B 树、B+ 树、B* 树谈到R 树
基本概念
提出背景
在大规模数据存储中,为实现索引查询,二叉查找树结构由于树的深度过大(二路结构导致存储节点有限!)而造成磁盘I/O读写过于频繁,进而导致查询效率低下。
在不减少查询数据量的情况下,为减少树的深度,采用多叉树结构。(磁盘查找存取的次数往往由树的高度所决定)
B 树(平衡多路查找树)是为了磁盘或其它存储设备而设计的一种多叉平衡查找树,与红黑树很相似,但在降低磁盘I/0操作方面要更好一些。许多数据库系统都一般使用B树或者B树的各种变形结构(B+、B*)来存储信息。
总结
B树:有序数组+平衡多叉树
B+树:有序数组链表+平衡多叉树
B*树:一棵丰满的B+树
B树
递归定义
一棵M阶的B树满足以下定义:
1. 树中每个结点最多含有m个孩子(m>=2)
2. 除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]个孩子
3. 若根结点不是叶子结点,则至少有2个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点)
4. 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部接点或查询失败的接点,实际上这些结点不存在,指向这些结点的指针都为null)
5. 每个非终端结点中包含有n个关键字信息: (n,P0,K1,P1,K2,P2,……,Kn,Pn)。其中: Ki (i=1…n)为关键字,且关键字按顺序升序排序K(i-1)< Ki。 Pi为指向子树根的接点,且指针P(i-1)指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)。关键字的个数n必须满足: [ceil(m / 2)-1]<= n <= m-1
B+树
应文件系统所需而产生的一种B-tree的变形树。
与B树的区别:
- .有n棵子树的结点中含有n-1 个关键字
- 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (而B 树的叶子节点并没有包括全部需要查找的信息)
- 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)
特点:
- 磁盘读写代价更低。内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
- 查询效率更加稳定。由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
B*树
B*tree是B+树的变体,在B+树的基础上(所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针),B*树中非根和非叶子结点再增加指向兄弟的指针;B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2)。
B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针。
B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针。
所以,B*树分配新结点的概率比B+树要低,空间使用率更高