目录
一. B-树(B树)
1. 基础知识
m阶-平衡树,一个节点有m个地址域,m-1个数据域,B树的所有叶子节点都在同一层。
B-树除根节点和叶子节点外,其他节点的孩子至少有一半个数的孩子(指针域)(比如说有5个孩子,ceil(5/2)=3,至少有3个孩子)
应用场景:文件索引系统的实现。
2. 插入操作
- 如果叶子结点空间足够,即该节点的关键字树小于m-1,则直接插入叶子结点的左边或右边(节点内的元素是有顺序的);
- 如果空间满了以至没有足够的空间去添加新元素,即该节点的关键字数已经有了m个,则需要将该节点进行“分裂”,将一半数量的关键字元素分裂到新的其相邻右节点中,中间元素上移到父节点中,而且当节点中关键元素向右移动了,相关的指针也需要向右移;
- 此外,如果在上述中间关键字上移到父节点的过程中,导致根节点空间满了,那么根节点也要进行分裂操作,这样原来的根节点中的中间关键字元素向上移动到新的根节点中,因此导致树的高度增加一层。
【例如】 插入以下字符字母到一颗空的5阶B树中:C、N、G、A、H、E、K、Q、M、F、W、L、T、Z、D、P、R、X、Y、S
分析:5阶B树,非根节点关键字个数n满足2<=n<=4,每个节点最多含有5个孩子,出根节点叶子结点外,其他节点至少有两个数据,三个指针域。
对于B树来说,分裂都是向上分裂的,是非常平衡的。所有的叶子节点都在同一层,且满足搜索树的性质
3. 删除操作
首先查找B树中要删除的元素,若元素存在,则进行删除,删除该元素后,需要判断该元素是否有左右孩子节点
- 如果有,则上移孩子节点中的相近元素(左孩子中最右边的节点或者右孩子中最左边的节点)到父节点中去。
- 如果没有,直接删除。
调整操作
删除元素,然后进行元素移动之后,如果节点关键字数目不满足条件(小于ceil(m/2)-1),则需要看其相邻的兄弟节点是否丰满(关键字个数大于ceil(m/2)-1)
- 如果丰满,则向父节点借一个元素来满足
- 如果其相邻兄弟都刚脱贫,即借了之后其节点数目小于ceil(m/2)-1,则该节点与其相邻的某一兄弟节点进行“合并”成一个节点,以此来满足条件。
依次删除H、T、R、E
4. B树的磁盘IO优势和搜索效率
Windows和linux的文件搜索,以及数据库的索引,都用到了B+树结构(修改原始B树结构可得到)
在系统中的数据搜索中:大量的数据都是存储在磁盘上的。为了增加系统的搜索速度,我们一般会给数据创建索引。而所谓的创建索引,就是给数据进行排序,然后进行数据搜索就更加快速。
如果想对磁盘上存储的数据进行快速搜索查找需要解决两个问题:
- 更少的磁盘I/O
- 更快的搜索算法
我们在这里采用m阶平衡树(节点分裂调平衡)。红黑树不是平衡树,是二阶非平衡树。
对比AVL树以及B树的效率
操作系统管理内存都是按页面(4K)分配,管理磁盘是按块(16K)分配。我们的文件在磁盘存储,从磁盘读1个文件,读4个字节,实际上,操作系统从磁盘是按block(16K)来读取的。
磁盘的最小单位是扇区,一个扇区大小为512字节,一个block是16K,16K除以512就是扇区的个数。
假如现在从磁盘读取10000000个索引,构建索引树,对比B树和AVL树的花费:
- 如果用AVL树(相当于2阶B树)构建索引树来存储10000000个索引,需要24层。一个节点就是存储着一个索引数据,最差情况就是每个节点的数据都不在一个磁盘block上,一个节点对应一个磁盘I/O,我们查找一个数据最多查找24次,即最多需要24次磁盘I/O。
- 同理,使用300阶B树,查找一次数据最多找3层,花费3次磁盘I/O。
故在构建索引树的时候,我们更多的使用B树
二. B+树
1. m阶B+树和m阶B树的区别
- B+树的非叶子节点只存数据,不存数据地址
- 索引项全部出现在叶子节点当中,索引项对应的数据也全部在叶子节点。所以在B+树中搜索每一个数据所花费的磁盘I/O一样,而B树是离根节点越近的搜索越快
- 把叶子结点,即全部数据串在一条链表中,这条链表是有序的。所以当我们进行区间查找及整表搜索不用遍历这颗复杂的B+树,直接遍历叶子结点即可。
- B+树存储的索引值是有重复的,且每个节点中的关键字是有序的
2. B+树比B树更适合操作系统的文件索引和数据库索引的原因
B+树的磁盘读写代价更低,B+树的内部节点没有指向关键字具体信息的指针,因此内部节点相对B树更小。如果把所有同一内部节点的关键字放在同一块磁盘中,盘块盘块所能容纳的关键字数量也就越多,一次性读入内存中的需要查找的关键字也就越多,相对IO读写次数降低。
例如,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一颗9阶B-tree(一个节点最多8个关键字)的内部节点需要两个盘块。而B+树内部节点只需要1个盘块。当需要把内部节点读入内存中的时候,B树就比B+树多一个盘块查找时间(在磁盘中就是盘片旋转的时间)
三. B*树
是B+树的变体,在B+树的非根节点和非叶子节点再增加指向兄弟节点的指针。
B*树定义了非叶子节点关键字个数至少为(2 / 3) * M,即块的最低使用率为2/3(代替B+树的1/2)
B+树和B*树的分裂比较
- B+树的分裂:当一个节点满时,分配一个新的节点,并将原节点中1/2的数据复制到新节点,最后在父节点中增加新节点的指针;B+树的分裂只影响原节点和父节点,而不会影响兄弟节点,所以它不需要指向兄弟的指针
- B*树的分裂:当一个节点满时,如果它的下一个兄弟节点未满,那么将一部分数据移到兄弟节点中,再在原节点插入关键字,最后修改父节点中兄弟节点的关键字(因为兄弟节点的关键字范围变了);如果兄弟也满了,则在原节点与兄弟节点之间增加新节点,并各复制1/3的数据到新节点,最后在父节点增加新节点的指针
所以B*树分配新节点的概率比B+树要低,空间使用率更高,在B+树基础上,为非叶子节点也增加链表指针,将节点的最低利用率从1/2提高到2/3。