B树、B+树与B*树

二叉树

在介绍B树之前,先来了解一下二叉树特点:

  • 若左子树不空,则左子树上所有节点的值均小于它的根节点值
  • 若右子树不空,则右子树上所有节点的值均大于它的根节点值
  • 它的左、右子树也分别为二叉排序数(具有递归性)

       从上图可以看出用二叉树组织数据时,查找比较方便,最好的情况可以减少一半的数据,但是最坏的情况是所有的节点都位于树的一侧时查找就不是那么快了,所以需要对二叉树进行平衡处理,于是可以转化为一个平衡二叉树,所谓的平衡就是指这棵树的各个分支的高度是均匀的,它的左子树和右子树的高度之差绝对值小于1,这样就不会出现所有元素都在一侧的情况了,在进行查找时总共比较节点的次数不超过树的高度(时间复杂度为O(logn))。

再来看看什么是B树
      B树,最早上由德国计算机科学家提出的,但为什么叫B树没有定论,也许是balance??。B树事实上是一种平衡的多叉查找树,也就是说最多可以开M个叉(m>=2),我们称之为m阶B树

例如,插入如下数据:
6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4


具体可以参考:https://www.cnblogs.com/vincently/p/4526560.html


总的来说,m阶B树满足以下条件:

  • 每个节点至多可以拥有m棵子树
  • 根节点至少有两个节点(极端情况,整个树就是一个节点除外)
  • 非根非叶的节点至少有ceil(m/2)个子树(ceil为向上取整)
  • 非叶子节点中的信息包括[n,a0,k1,a1,k2,a2,...kn,an]其中n表示该节点中保存的关键字个数,K为关键字且Ki<Ki+1,a为指向子树根节点的指针。
  • 从根到叶子的每条路径都有相同的长度,也就是说,叶子节点在相同的层,并且这些节点不带信息,实际上这步表示找不到指定的值,也就是指向这些节点的指针为空。

       B树的查询过程和二叉树排序树比较类似,从根节点依次比较每个结点,因为每个节点中的关键字和左右子树都是有序的,所以只要比较节点中的关键字,或者沿着指针就能很快地找到指定的关键字,如果查找失败,则返回叶子节点即null。
例如上图中的5阶B树,若要查找K:

  1. 从根节点P开始,K的位置在P之前,进入左侧指针
  2. 左子树中,依次比较C\F\J\M,发现K在J\M之间
  3. 沿着J\M之间的指针,继续访问子树,并依次比较,发现第一个关键字K就是要找的值

 

B+树,B树的plus版本

与B树的差异在于:

  • 有n棵子树的节点含有n个关键字
  • 非叶子结点仅具有索引的作用,跟记录有关的信息均存放在叶子结点。
  • 树的所有叶子结点构成一个有序链表,可以按照关键字排序的次序遍历全部记录。

总之,B和B+树的主要区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点节点使用链表相连,便于区间查找和遍历。

优点

  •  由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好 的缓存命中率。
  • B+树的叶子结点都是相连的,因此对整个树的遍历只需要一次结尾遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中率没有B+树好。
  • 但B树的优点在于每个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速

分析结果:
       对于一棵节点个数为N,阶为M的子树,查找和插入需要logM-1(N) ~ logM/2(N)次比较.,对于阶为M的B树,每个节点的子节点个数为M/2至M-1之间,所以树的高度在logM-1(N) ~ logM/2(N)之间。这种效率是很高的,对于N=680亿个节点,如果设置阶M=1024,则logM/2(N)=log512(680亿)<=4,即在680亿个元素中,如果这棵树的阶为1024,则只需要小于4次即可定位到该节点,然后再采用二分查找即可找到值。

 

应用

      B与B+树广泛用于文件存储系统以及数据库系统中,例如mysql。
      在讲mysql为什么选择b+树前先来看看一般磁盘是如何构成的:
       计算机的主存基本都是随机访问存储器(Random-Access Memory, RAM),它又可以分为两类:静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)。静态比动态快,但是成本也要多很多,一般作为CPU的高速缓存,DRAM作为内存。这类存储器他们的结构和存储原理比较复杂,基本是使用电信号来保存信息的,不存在机器操作,所以访问速度非常快,但如果断电数据就会丢失。
       所以一般使用的是磁盘存储大量的数据,但它的读取速度比较慢,一般为毫秒级,DRAM要比磁盘快10万倍,SRAM要比磁盘快100万倍。
       磁盘是由盘面和磁头构成,盘面有磁性材料,通过旋转和移动磁头即可读取数据,通常旋转速度是5400或7200转。同心贺称为磁道,每个磁道被划分为了一组扇区,每个扇区包含相等数量的数据位通常是512字节。扇区之间是有间隔的。对磁盘的访问时间分类寻道时间,旋转时间和传送时间。
由于磁盘的特性存取比较慢,再加上机械运动耗时,因此为了提高效率,要尽量减少磁盘IO,减少读写操作。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存(依据:当一个数据库用到时,其附近的数据也通常会马上被使用)。因顺序读取的效率很高,因此对于有局部性程序来说,预读可以提高IO效率。
       预读的长度一般称为页(page)的整数倍。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在很多操作系统中,页的大小通常为4K),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。
       文件系统及数据系统的设计者利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次IO就可以完全载入。为了达到这个目的,在实际实现B树还需要使用如下的技巧:

  • 每次新建一个节点的同时,直接申请一个页的空间(512或1024),这样就保证一个节点物理上也存储在一个页里,再就是计算机存储分配都是按页对齐的,就实现了一个node只需要一次IO。如上例,将B树的度M设置为1024,这样600亿的数据中只需要小于4次查找即可定位到某一存储位置。
  • 在B+树中内节点只存储导航用到的key,并不存储具体值,这样内节点个数较少,能够全部读取到主存中,外结点存储key及值,并且顺序排列,具有良好的空间局部性,所以B或B+树比较适合文件系统的数据结构
  • 另外B\B+也经常用做数据库的索引,如mysql的innodb索引等等

       B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)。

综上所述,用B-Tree作为索引结构效率是非常高的。

       而红黑树这种结构,h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O渐进复杂度也为O(h),效率明显比B-Tree差很多。

      上文还说过,B+Tree更适合外存索引,原因和内节点出度d有关。从上面分析可以看到,d越大索引的性能越好,而出度的上限取决于节点内key和data的大小:

                                       dmax=floor(pagesize/(keysize+datasize+pointsize))
floor表示向下取整。由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。


B*树

       是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针,B*树定义了非叶子结点关键字个数至少为(2/3)M,即块的最低使用率为2/3比B+树的1/2要好。

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

猜你喜欢

转载自blog.csdn.net/xpsallwell/article/details/86229844