理论基础 —— 索引 —— B 树、B+ 树与 B* 树

【B 树】

B 树(B-Tree)也写作 B-树,其是一种平衡的多路查找树,主要面向于动态查找,常用于文件系统中。

B 树中,结点最大的孩子数目称为 B 树的阶,2-3 树是 B 树的特例,其是 3 阶 B 树。

B 树的查找、插入、删除操作与 2-3 树相似。 

一棵 m 阶的 B 树或为空树,或为满足以下性质的 m 叉树:

  • 所有的叶结点都出现在同一层,且不带信息
  • 树中每个结点最多有 m 棵子树
  • 叶结点的父结点称为终端结点,若根结点不是终端结点,则至少有两棵子树
  • 除根结点外的所有非终端结点至少有 \left \lceil \frac{m}{2} \right \rceil 棵子树
  • 所有的非终端结点都包括数据:\{n,A_0,K_1,A_1,K_2,...,K_n,A_n\},其中 n 为关键码个数,Ki 为关键码,Ai 为指向子树根结点的指针,且指针 Ai 所指子树中所有结点的关键码均小于 K_{i+1} 大于 Ki

一般情况下,B 树的叶结点可以看做外部结点,即查找失败的点,实际上这些结点并不存在,指向这些结点的指针为空。

因此,B 树的叶结点可以不画出来,由于叶子都出现在一层上行,所以 B 树也是树高平衡的,此外,B 树中每个结点中关键码的个数为子树个数 -1

如下图,是一个 3 阶 B 树。

在实际应用中,常使得 B 树的阶数与磁盘存储的页面大小匹配。

例如一棵 B 树的阶为 1001,即 1 个结点包含 1000 个关键字,高度为 2,其可以存储超过 10 亿个关键字,只要让根结点持久地保留在内存中,那么在这棵树上,寻找某一关键字至多需要两次硬盘的读取。

通过这种方式,在有限内存的情况下,每一次磁盘访问都可以获取大量的数据,这极大的减少了内存外存交换数据次数的频率,提高了时间效率,可以说,B 树的数据结构就是为了内外存交换而准备的。

【B+ 树】

B+ 树是 B 树的变形树,严格意义上来讲,其已经不是一棵树了。

在 B 树中,每一元素在该树中只出现一次,有可能在叶结点上,也有可能在分支结点上,而在 B+ 树中,出现在分支结点中的元素会被当作他们在该分支结点位置的中序后继者中再次列出,此外,每一叶结点都会保存一个指向后一叶结点的指针

一棵 m 阶的 B+ 树与一棵 m 阶的 B 树差异在于:

  • 在 n 棵子树中包含有 n 个关键字
  • 所有分支结点可看作是索引,结点中仅含有其子树中最大/最小的关键字
  • 所有叶结点包含全部关键字信息,以及指向这些关键字记录的指针,叶结点本身依关键字的大小自大到小顺序连接

如下图,是一个 3 阶 B+ 树。

B+ 树的数据结构好处在于,只要是随机查找,就从根结点出发进行查找,只不过在分支结点找到了待查找的关键字,其也只是用于索引的,不能提供实际记录的访问,仍需到达包含此关键字的终端结点。此外,B+ 树的结构特别适合带有范围的查找。

B+ 树的插入、删除过程与 B 树类似,不过插入、删除的元素都在叶结点上进行。

【B* 树】

B* 树是 B+ 树的变形树,其也非严格意义上的树。

B*  树在 B+ 树的基础上进行了改进,在 B+ 树的非根和非叶子结点上行增加了指向兄弟的指针,且规定非叶结点关键字个数至少为 \frac{2}{3} m,即块的最低使用率为 2/3(B+ 树只有 1/2),可以看出,B* 树分配结点的概率要比 B+ 树低,空间利用率更高。