B+树、B*树详解

推荐:https://ivanzz1001.github.io/records/post/data-structure/2018/06/16/ds-bplustree

一、B树和B+树的区别

这都是由于B+树和B具有这不同的存储结构所造成的区别,以一个m阶树为例。

(1)关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B树虽然也有m个子结点,但是其只拥有m-1个关键字。

(2)存储的位置不同;B+树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。

(3)分支结点的构造不同;B+树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。

(4)查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。

二、B+树的定义:

1、其定义基本与B-树同;

2、非叶子结点的子树指针与关键字个数相同; 

3、非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间); 

4、为所有叶子结点增加一个链指针;

5、所有关键字都在叶子结点出现。

B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;

三、B+的特性:

1、所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;

2、不可能在非叶子结点命中;

3、非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;

4、更适合文件索引系统。

四、B+树的操作:查询、插入、删除

   1、查询:有两种查找运算,

(1)从最小关键字起顺序查找;

(2)从根节点开始,进行随机查找

在查找时,若非终端节点上的关键字等于给定值,并不终止,而是继续向下直到叶子节点。因此,在B+树中,不管查找成功与否,每次查找都是走了一条从根到叶子节点的路径。其余同B-树的查找类似。

B+树的优势在于查找效率上,下面我们做一具体说明:

  首先,B+树的查找和B树一样,类似于二叉查找树。起始于根节点,自顶向下遍历树,选择其分离值在要查找值的任意一边的子指针。在节点内部典型的使用是二分查找来确定这个位置。

  (1)、不同的是,B+树中间节点没有卫星数据(索引元素所指向的数据记录),只有索引,而B树每个结点中的每个关键字都有卫星数据;这就意味着同样的大小的磁盘页可以容纳更多节点元素,在相同的数据量下,B+树更加“矮胖”,IO操作更少

  B树的卫星数据:

B+树、B*树详解_第1张图片

  B+树的卫星数据:

B+树、B*树详解_第2张图片

  需要补充的是,在数据库的聚集索引(Clustered Index)中,叶子节点直接包含卫星数据。在非聚集索引(NonClustered Index)中,叶子节点带有指向卫星数据的指针。

  (2)、其次,因为卫星数据的不同,导致查询过程也不同;B树的查找只需找到匹配元素即可,最好情况下查找到根节点,最坏情况下查找到叶子结点,所说性能很不稳定,而B+树每次必须查找到叶子结点,性能稳定

  (3)、在范围查询方面,B+树的优势更加明显

  B树的范围查找需要不断依赖中序遍历。首先二分查找到范围下限,在不断通过中序遍历,知道查找到范围的上限即可。整个过程比较耗时。

  而B+树的范围查找则简单了许多。首先通过二分查找,找到范围下限,然后同过叶子结点的链表顺序遍历,直至找到上限即可,整个过程简单许多,效率也比较高。

例如:同样查找范围[3-11],两者的查询过程如下: 

B树的查找过程: 

B+树、B*树详解_第3张图片

B+树的查找过程:

B+树、B*树详解_第4张图片

2、插入:

   B+树的插入与B树的插入过程类似。不同的是B+树在叶结点上进行,如果叶结点中的关键码个数超过m,就必须分裂成关键码数目大致相同的两个结点,并保证上层结点中有这两个结点的最大关键码。

3、删除:

  B+树中的关键码在叶结点层删除后,其在上层的复本可以保留,作为一个”分解关键码”存在,如果因为删除而造成结点中关键码数小于ceil(m/2),其处理过程与B-树的处理一样。在此,我就不多做介绍了。

小结】B+树相比B树的优势: 

1.单一节点存储更多的元素,使得查询的IO次数更少; 

2.所有查询都要查找到叶子节点,查询性能稳定; 

3.所有叶子节点形成有序链表,便于范围查询。

五、B+树的用途:

B+树主要适用于索引操作。为什么说B+树比B-树根适合实际应用中操作系统的文件索引和数据库索引?

B+树的磁盘读写代价更低: B+树的内部节点并没有指向关键字具体信息的指针。因此其内部节点先对B-树更小。如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。举个例子:假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes, 一个关键字具体信息指针2bytes。一棵9阶B-树(一个节点最多8个关键字)的内部节点需要2个盘块。而B+树内部节点只需要1个盘块。当需要把内部节点读入内存的时候,B-树就比B+树多一次盘块查找时间(在磁盘中就是盘片旋转时间)

B+树的查询效率更加稳定: 由于非终节点并不是最终指向文件内容的节点,而只是叶子节点中关键字的索引。所以任何关键字的查找必须走一条从根节点到叶子节点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

【注】:Mysql中B+树的应用:详见:https://ivanzz1001.github.io/records/post/data-structure/2018/06/16/ds-bplustree

六、B*树

是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针。

B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);

B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;所以,B*树分配新结点的概率比B+树要低,空间使用率更高。

B+树、B*树详解_第5张图片

【小结】

B-树:

多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;

B+树:

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

B*树:

在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3;

【注】不同树的实际应用场景:

1、AVL树的应用:

最早的平衡二叉树之一。应用相对其他数据结构比较少。windows对进程地址空间的管理用到了AVL树。

2、红黑树的应用:

红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。

例如,Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。

3、B+树的应用:详见B+树

你可能感兴趣的:(B+树、B*树详解)