二叉搜索树、平衡二叉树、红黑树都属于二叉树,由于每个节点只有一个元素,每个节点只能连接两个节点。对于像文件系统、数据库这样需要数目庞大读写删除操作,如果采用这种数据结构将会大大降低系统的效率,遇到I/O读写的瓶颈。
有没有一种数据结构可以降低往返于节点之间的次数呢?答案是有的,就是B树。什么是叫B树呢?和二叉树有啥区别呢?
了然于心二叉树的劣势,这个B树每个节点必然不是只是含有一个节点了,可以进行多路查找,B树是一种平衡的多路查找树。下图就是一颗B树
一个m阶的B树,满足以下性质:
1、非叶子节点至少有两个子树,最多m个子树;
2、非根的分支节点,有k-1个键值,有k个子树,并且按照升序进行排列。每一个叶子节点均含有k-1个元素。这里的([m/2]<k<=m),这里的m/2需要向上取整;
3、所有的叶子节点位于同一层次;
4、每一个节点含有键值、指向子树指针等相关的信息。
提到B树,既然是一颗树,就对树的元素的插入与删除比较感兴趣。
插入过程:
首先是在恰当的叶子结点中添加关键码,如果该结点中关键码不超过m-1个,则插入成功。
否则要把这个结点分裂为两个。并把中间的一个关键码拿出来插到结点的父结点里去。父结点也可能是满的,就需要再分裂,再往上插。
最坏的情况,这个过程可能一直传到根,如果需要分裂根,由于根是没有父结点的,这时就建立一个新的根结点。
插入可能导致B树朝着根的方向生长。
下面以一棵阶数为3的B树为例,说说B树的插入与删除的过程,此过程中每次的插入或者删除都要调整B树使得满足B树的几个条件。
节点的删除过程(红色标记的为待删除的节点)如下:
以上为3阶B树的插入删除过程的一个例子,可以在此基础上进行体会。在实际的生产中一般B树的阶数很大,具体的删除规则与此类似。
B树使用最多的场景是文件系统与数据库,必然涉及到查找。那么一个含有n个元素,m阶的B树的最大深度是多少?
第一层含有1个节点;
第二层至少有两个节点,由于分支节点至少含有[m/2]个节点。
第三层至少有2*[m/2]个节点;
第四层至少有2*[m/2]*[m/2];
以此类推。。。
第k层至少有个节点;
则叶子层k+1层有个节点。
查找不成功的次数为n+1。则有如下结果:
所以含有n个关键元素的m阶B树的深度最大为:
下面看B树的另一种方式B+树。
首先看下B+树的形式:
观察此树每个节点的内容可以看出,子节点包含父节点的所有关键字。所以所有的叶子节点包含全部的关键字信息。
所以B+树相对于B树的变化的情况是:
有n棵子树的节点含有n个关键字;
所有的叶子节点含有所有的关键字,以及指向包含这些关键字记录的指针,叶子节点本身的关键字自小到大依次排列;
所有分支节点都看成是索引,节点中仅仅含有其子树中最大或者最小的关键字。
看了B树与B+树的不同,在进行查找的时候,利用B树需要频繁地往返于每个节点之间,这就意味着,我们必须经过多次的访问才能得到结果,每次遍历的时候可能要频繁经过某个节点,这降低了查找的性能。而B+可以解决重复访问节点的问题。
B+树查找的时候,如果从根节点开始遍历查找关键字,在节点处查找到结果,这个也只是用以索引,不是真实的访问记录,还是要遍历到叶子节点才结束;若从小到大查找,只需要从叶子节点的最左端开始,顺序遍历就可以得到。
B+树的一缺点:B+树最大的性能问题是会产生大量的随机IO,随着新数据的插入,叶子节点会慢慢分裂,逻辑上连续的叶子节点在物理上往往不连续,甚至分离的很远,但做范围查询时,会产生大量读随机IO。解决B+树这一问题可采用LSM树,即Log-Structured Merge-Trees。其中Hbase中就利用LSM,感兴趣可以多查查LSM树相关的知识,这里就不再赘述。