BST、B树、B+_树

    B树和B+_树被广泛应用于关系型存储引擎中。下面结合参考文章,对该内容作简要总结。

1.  BST树 & AVL树

      1.1 二叉搜索树(BST binary search tree)特性:

        1. 所有非叶子结点之多拥有两个儿子(left、right);         2. 所有结点存储一个关键字;         3. 非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树。        
《BST、B树、B+_树》

        BST的搜索结果,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中;否则,如果查询关键字比结点关键字小,就进入左子树;如果比结点关键字大,就进入右子树;如果左子树或者右子树的指针为空,则报告找不到相应的关键字。         如果BST的所有非叶子结点的左右子树的节点数目保持差不多平衡,那么B树的搜索性能逼近二分查找;但它相比较连续内存空间的二分查找的优点是:改变BST结构(插入与删除节点)不需要移动大段的内存数据,甚至通常是常熟的开销。         但是BST经过多次插入与删除后,有可能导致不同的结构:        
《BST、B树、B+_树》

        右边也是一个BST,但它的搜索性能已经是线性的了;同样的关键字集合有可能导致不同的树结构索引;所以使用BST还要考虑尽可能让BST保持左图的结构,和避免右图的结构,也就是所谓的“平衡”问题。

      1.2 平衡二叉树概念:

        平衡二叉树(AVL树,发明者为Adel’son-Vel’skii 和 Landis),除了具备二叉查找树的基本特征之外,还具备一个非常重要的特点:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值(平衡因子)不超过1。也就是说AVL树每个节点的平衡因子只可能是-1,0,1。         实际使用的BST都是在原BST基础上加上平衡算法,即“平衡二叉树”;如何保持BST节点分布均匀的平衡算法是平衡二叉树的关键;平衡算法是一种在BST中插入和删除结点的策略。

      1.3 平衡二叉树的平衡策略:

        如何在二叉搜索树中添加数据的同时保持平衡呢?基本思想是:当在二叉排序树中插入一个节点时,首先检查是否因插入而破坏了平衡,若破坏,则找出其中的最小不平衡二叉树,在保持二叉排序树特性的情况下,调整最小不平衡子树中节点之间的关系,以达到新的平衡。所谓最小不平衡子树指离插入节点最近且以平衡因子的绝对值大于1的节点作为根的子树。

2.  B树(B-Tree/B-树/B_树/多路搜索树)

     2.1 B树的特性:

        B树中每一个结点能包含的关键字的数目有一个上界和下界。这个下界可以用一个称作B树的
最小度数(即结点中最小孩子数据)M(M >= 2)表示, 这里该B树也成为M阶的B树。         1. 定义任意非叶子结点最多只有M个儿子(M >= 2);         2. 根结点的儿子数为【2, M】;         3. 除根结点以外的非叶子结点的儿子数为【
M/2
 
M】;         4. 每个结点至少存放
M/2-1(取上整数)和至多
M-1个关键字;(至少2个关键字);         5. 非叶子结点的关键字个数 = 指向儿子的指针个数 – 1;         6. 非叶子结点的关键字:k[1], k[2], … , K[M-1];且 K[i] < k[i+1] ;         7. 非叶子结点的指针:P[1],  P[2], … , P[M]; 其中P[1]指向关键字小于k[1]的子树,P[M]指向关键字大于K[M-1]的子树,其他P[i]指向关键字属于(K[i-1], K[i])的子树;         8. 所有叶子结点位于同一层。        
《BST、B树、B+_树》

     2.2  B-树的特点:

        1. 关键字集合分布在整棵树中;         2. 任何一个关键字出现且只出在一个节点中;         3. 搜索有可能在非叶子结点结束;         4. 其搜索性能等价于在关键字全集内做一次二分查找;         5. 自动层次控制;         由于限制了除根结点以外的非叶子结点,至少含有 M/2个儿子,确保了结点最低的搜索性能为:        
《BST、B树、B+_树》

        其中,M为设定的非叶子结点最多子树个数, N为关键字总数。前一个因子一个结点中每个关键字找到的时间复杂度,后一个因子表示找到相应关键字结点的时间复杂度;         所以B-树的性能总是等价于二分查找(
与M值无关),也就没有B树平衡的问题;         由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占M/2的结点;删除结点时,需要将两个不足M/2的兄弟结点合并。

    2.3  文件查找的具体过程(涉及磁盘IO操作)

        磁盘读取数据时以盘块(block)为基本单位的,位于同一个盘块中的所有数据都能被一次性全部读取出来。而磁盘IO代价主要化肥在查找时间上,因此我们应该尽量将相关信息存放在同一盘块,同一个磁道中。或者至少放在同一个柱面或相邻柱面上,以求在读/写信息时尽量减少磁头来回移动的次数,避免过多的查找时间。         B树中的每个结点根据实际情况可以包含大量的关键字信息和分支(当然是不能超过磁盘块的大小,根据磁盘驱动的不同,一般盘块的大小为1K-4K左右),这样树的深度降低了,就意味着查找一个元素只要很少结点从硬盘中读入内存,很快访问到要查找的数据。            为简单起见,这里用少量数据结构构造一棵3叉树的形式,实际应用中用的B树结点中关键字很多。其结构简单定义为:


    typedef struct {

    int file_num;   
/*文件数*/
    char * file_name[max_file_num];
  /*文件名(key)*/
    BTNode * BTptr[max_file_num+1];
 /*指向子节点的指针*/
    FILE_HARD_ADDR offset[max_file_num]; 
/*文件在硬盘中的存储位置*/

    }BTNode;

        如果每个盘块可以正好存放一个B树的结点(整个存放2个文件名),那么一个BTNode结点就代表一个盘块,而子树指针就是存放另一个盘块的地址。         下面,模拟查找文件29的过程:          1. 根据根结点指针找到文件目录的根磁盘块1, 将其中的信息导入内存。【磁盘IO操作1次】          2.此时内存中有两个文件名17、35和三个存储其他磁盘页面地址的数据,根据算法我们发现,17<29<35, 因此我们找到指针P2。         3. 根据P2指针,我们定位到磁盘块3,将其中的信息导入内存。【磁盘IO操作2次】         4.此时内存中有两个文件名26、30和三个存储其他磁盘页面地址的数据,根据算法我们发现,26<29<30, 因此我们找到指针P2。         5. 根据P2指针,我们定位到磁盘块8,将其中的信息导入内存。【磁盘IO操作3次】         6. 此时内存中有两个文件名28,29。根据算法我们查找到文件名29,并定位了该文件内存的磁盘地址。         可以看到,
B树上大部分操作所需的磁盘存取次数与B树的高度成正比。文件越多,B树比平衡二叉树所用的磁盘IO操作次数将越少,效率也越高。

    2.4  B树的高度

        根据上面例子我们可以看出,对于外存做IO读的次数取决于B树的高度。而B树的高度由什么决定的呢?    算法导论第18章关于B树的高度一节内容,有如下定理:         如果n>=1,则对于任意一棵包含n个关键字、高度为h,最小度数 t>=2的B树,有:                     
《BST、B树、B+_树》

        具体推到过程可以见算法导论定理 18.1。

3.  B+_树(B+_Tree/多路搜索树)

        B+_树是B树的变体,也是一个多路搜索树。

   3.1 B+_树的概念:

        1. 其定义基本与B树相同,当然也有区别;         2. 非叶子结点的子树指针与关键字个数相同;         3. 非叶子结点的子树指针P[i],指向关键字值属于 【K[i], K[i+1])的子树(注意B树两边都是开区间的),所有的非叶子结点可以看成是索引部分;         4. 为所有叶子结点增加一个链指针(有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点);         5. 所有关键字都在叶子结点出现。        
《BST、B树、B+_树》

    3.2  B+_树与B树的比较:   

       B+_树的搜索与B树叶基本相同,区别是B+_树只有达到叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找。        但B+_树比B树更适合实际应用中操作系统的文件索引和数据库索引,原因如下:        1) B+_tree的磁盘读写代价更低        B+_tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。           举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B树(一个结点最多 8个关键字)的内部结点需要2个盘快。而B+_树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+ 树多一次盘块查找时间(在 磁盘中就是盘片旋转的时间)。          2) B+_tree的查询效率更加稳定         由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。        3) B+_树还有一个最大的好处,方便扫库,B树必须用中序遍历的方法按序扫库,而B+_树直接从叶子结点挨个扫一遍就完了,B+_树支持range-query非常方便,而B树不支持。这是数据库选用B+_树的最主要原因。

     3.3  B+_树的特性:

        1. 所有关键字都出现在爱叶子结点的链表中(稠密索引),且链表中关键字恰好是有序的;         2. 不可能在非叶子结点命中;         3. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储关键字数据的数据层;         4. 更适合文件索引系统。        

参考阅读: 二叉排序树的详细实现     http://blog.csdn.net/touch_2011/article/details/6831924

B树、B-树、B+树、B*树 http://www.cnblogs.com/oldhorse/archive/2009/11/16/1604009.html

B树、B-树、B+树、B*树 http://blog.csdn.net/v_JULY_v/article/details/6530142

    原文作者:B树
    原文地址: https://blog.csdn.net/mdpmvpmao/article/details/38305217
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞