在大型的数据库存储中,实现索引查找,如果采用二叉查找树的查找的话,由于节点的存储数据是有限的(不可能将节点存储过多的数据,否则就变成线性的查找了),这样如果数据量很大的,就会导致树的深度过大从而造成磁盘IO操作过于频繁,就会导致效率非常低下。

我们知道动态查找树能够提高查找效率,比如:二叉查找树,平衡二叉查找树,红黑树。他们查找效率的时间复杂度O(log2n),跟树的深度有关系,所以为了提高索引查找的效率,可以通过减少树的深度,其中基本思想是:采用多叉树结构

B-树

B-树就是我们平常说的B树,不要读成B减树了,它在文件系统中很有用。

B-树的定义和特点

一个m阶的B-树为满足下列条件的m叉树:

1、每个分支最多有m棵子树(关键字最多m-1个);

2、除根结点外,每个分支结点最少有[ m/2 ] ( 向上取整 )棵子树;

3、根结点最少有两棵子树(除非根结点为叶结点,此时B-树只有一个叶结点);

4、所有的叶子结点都位于同一层,叶结点不包含任何关键字信息;

5、所有分支结点(非终端结点)中包含如下信息:

文件(四)——B-树和B+树_B-树


其中,n为结点中关键字值的个数, n<=m

keyi为关键字,且满足 keyi<keyi+1 ,1<=i<n

pi为指向该结点的第i+1棵子树的根的指针,0<=i<=n,(pi指的结点中所有关键字值都大于keyi)如下为一个4阶B-树:

文件(四)——B-树和B+树_结点分裂_02


对于该4阶B-树,有如下特点:

1、每个分支结点最多有4棵子树(即最多有m-1个关键字值);

2、每个分支结点最少有2棵子树;

3、根结点最少有2棵子树;

4、所有“叶结点”都在同一层上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)

B-树的查找

首先将给定的关键字k在B-树的根结点的关键字集合中采用顺序查找法或者折半查找法进行查找,若有k=keyi , 则查找成功,根据相应的指针取得记录。否则,若k<keyi,则在指针pi-1所指的结点中重复上述查找过程,直到在某结点中查找成功,或者有pi-1=NULL,查找失败。(类似于二叉排序树的查找)

如下图:

文件(四)——B-树和B+树_插入_03

C语言实现如下:

#define//B-树最大的阶数
typedef struct node {
int keynum; //关键字个数
keytype key[M+1]; //关键字集合 key[0]未使用,关键字下标从1开始
struct node *ptr[M+1]; //子树根结点集合
rectype *recptr[M+1]; //存储位置集合
}*BTree;

B-树查找算法实现如下:

keytype MBSEARCH(Btree T,keytype k)
{
int i,n;
BTree p=T;
while(p!=NULL){
n=p->keynum;
p->key[n+1]=Maxkey;
i=1; //在P指结点的关键字集合中查找k
while(k>p->key[i])
i++;
if(p->key[i]==k)
return p->key[i];
else
p=p->ptr[i-1]; //在pi-1指的结点中查找
}
return -1;
}

B-树的插入

由于一棵m阶B-树的结点中最多有m-1个关键字值,所以当关键字数目超过m-1时,需要进行结点分裂,基本思想如下:

若将k插入到某结点后使得该结点中关键字值数目超过m-1时,则要以该结点位置居中的那个关键字值为界将该结点一分为二,产生一个新结点,并把位置居中的那个关键字值插入到双亲结点中;如双亲结点也出现上述情况,则需要再次进行分裂。最坏情况下,需要一直分裂到根结点,以致于使得B-树的深度加1。

B-树的生成从空树开始,即逐个在叶结点中插入结点(关键字)而得到。

插入结点(关键字)的步骤如下:

一个原始的B-树阶为3,如下图:

文件(四)——B-树和B+树_插入_04

首先,我需要插入一个关键字:30,可以得到如下的结果:

文件(四)——B-树和B+树_查找_05


再插入26,得到如下的结果:

文件(四)——B-树和B+树_B-树_06


OK,此时如图所示,在插入的那个终端结点中,它的关键字数已经超过了m-1=2,所以我们需要对结点进分裂,所以我们先对关键字排序,得到:26 30 37 ,所以它的左部分为(不包括中间值):26,中间值为:30,右部为:37,左部放在原来的结点,右部放入新的结点,而中间值则插入到父结点,并且父结点会产生一个新的指针,指向新的结点的位置,如下图所示:

文件(四)——B-树和B+树_B-树_07


OK,然后我们继续插入新的关键字:85,得到如下图结果:

文件(四)——B-树和B+树_插入_08


正如图所示,我需要对刚才插入的那个结点进行“分裂”操作,操作方式和之前的一样,得到的结果如下:

文件(四)——B-树和B+树_查找_09


哦,当我们分裂完后,突然发现之前的那个结点的父亲结点的度为4了,说明它的关键字数超过了m-1,所以需要对其父结点进行“分裂”操作,得到如下的结果:

文件(四)——B-树和B+树_查找_10

该部分内容参考自:​​五分钟搞懂什么是B-树(全程图解)​

B+树

B+树的定义

一个m阶的B+树为满足下列条件的m叉树:

文件(四)——B-树和B+树_插入_11


B+树 可以看做是 在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;B+树的实例如下:

文件(四)——B-树和B+树_B-树_12

B-树与B+树的区别

从结构上看,B-树和B+树有如下区别:
1、B-树的每个分支结点中含有该结点中关键字值的个数,B+树没有;
2、B-树的每个分支结点中含有指向关键字值对应记录的指针,而B+树只有叶结点有指向关键字值对应记录的指针;
3、B-树只有一个指向根结点的入口, 而B+树的叶结点被链接成为一个不等长的链表, 因此,B+树有两个入口,一个指向根结点,另一个指向最左边的叶结点(即最小关键字所在的叶结点)。