B树

来源:互联网 发布:民生银行软件有问题 编辑:程序博客网 时间:2024/06/11 20:23

B树

B树是为了磁盘或其它存储设备而设计的一种多叉多关键字平衡查找树。与红黑树有一定相似之处(红黑树参考:链接),但在降低磁盘I/0操作方面要更好一些。许多数据库系统在存储信息时,都使用了B树或者B树的各种变形结构,如B+树,B*树等。

一棵m阶B树是一棵平衡的m路搜索树。它或者是空树,或者是满足下列性质的树:

1、根结点至少有两个子女;(不管几阶,根节点最少两个子节点就行)
2、每个非根节点所包含的关键字个数 j 满足:┌m/2┐ - 1 <= j <= m - 1;(符号表示向上取整)
3、除根结点以外的所有结点(不包括叶子结点)的度数是关键字总数加1,故内部子树个数 k 满足:┌m/2┐ <= k <= m ;
4、所有的叶子结点都位于同一层。(强制平衡)
注:这里的叶子节点和红黑树中的叶子节点类似,指的是空节点。

在B树中,每个结点中关键字从小到大排列,并且当该结点的孩子是非叶子结点时,该k-1个关键字正好是k个孩子包含的关键字的值域的分划。因为叶子结点不包含关键字,所以可以把叶子结点看成在树里实际上并不存在的外部结点,指向这些外部结点的指针为空,叶子结点的数目正好等于树中所包含的关键字总个数加1。
B树中的一个包含n个关键字,n+1个指针的结点的一般形式为: (n,P0,K1,P1,K2,P2,…,Kn,Pn)
其中,Ki为关键字,K1<K2<…<Kn, Pi 是指向包括Ki到Ki+1之间的关键字的子树的指针。



然而,《算法导论》中的定义与上面的有出入。主要是在关键字的数量,以及子节点的度数描述上。《算法导论》没有用阶来描述,而是改用最小度数t。最小度是指一个非空节点最少孩子节点的数目(t>=2)。与上面定义不同的地方在于:
1.每个非根的内结点至少有t个子女,每个非根的结点必须至少含有t-1个关键字,如果树是非空的,则根结点至少包含一个关键字;
2.每个结点可包含至多2t-1个关键字。所以一个内结点至多可有2t个子女。如果一个结点恰好有2t-1个关键字,我们就说这个结点是满的;
3.当关键字数t=2时的B树是最简单的。此时,每个内结点可能因此而含有2个或3个或4个子女,亦即一棵2-3-4树,然而在实际中,通常采用大得多的t值。
可以看出,这种描述方法,排除了2阶、3阶B树,最简单的B树变为2-3-4树。同时,最多节点数是最少节点数多2倍,这样也就排除了所有的奇数阶树。

在实际代码实现中,每个节点只需用两个列表分别记住该节点的关键字以及子节点即可(非降序),不用记录子节点与关键字相互之间的顺序关系。在对B树操作时,如果要定位一个值在关键字中,还是在子节点中,只需采用合理的查找方法,如果不在关键字中,记录关键字间隔的位置,也就是子节点的位置,就能找到具体是哪个子节点。这里不再赘述B树的实现,也就不贴代码了。(另有相关代码,本文从略)
另外,虽然没有明确提出,但是《算法导论》中在实现B树时排除了空节点,叶节点也是有关键字的,只是没有子节点。B树和红黑树不同,红黑树需要这些空节点来计算黑色高度,从而保证平衡性。但是B树的插入规则,保证B树肯定是平衡的,所以没必要把空节点体现在数据结构中,增加代码的复杂性。

查找
B树上的查找是一个查找结点和在结点内的关键字中查找交叉进行的过程。从根结点开始,在结点包含的关键字中查找给定的关键字,找到则查找成功;否则确定给定关键字可能在的子树,重复上面的操作,直到查找成功或者确定为空为止。 下图显示了在B树中查找关键字“21”的过程。 (注:这里是顺序查找,实际上二分查找似乎更好,二分查找也能准确定位到子节点的位置)


插入
B树虽然描述起来挺复杂,但实现起来却比红黑树简单不少。首先是通过查找,找到可插入的恰当位置。在恰当的叶子结点中添加关键字,如果该结点中关键字不超过最大度数,则插入成功。否则要把这个结点分裂为两个。并把中间的一个关键字拿出来插到结点的父结点里去。父结点也可能是满的,就需要再分裂,如此循环到父节点不需要分裂。最坏的情况,这个过程可能一直传到根节点,如果需要分裂根,由于根是没有父结点的,这时就建立一个新的根结点,整个树高度加1。下图显示了在B树(5阶)中插入关键字33的过程。(图中是先分裂最后再插入关键字33,也可以先插入再分裂,效果一样)
当然也可以换个思路,查找关键字的时候就从上到下判断,先判断根节点有没有满,有的话先分裂根节点,没有则不做操作。然后重复操作,即先分裂已满的子节点,然后在分裂后的子节点中去判断。这样树的高度需不需要加1,一开始就知道,同时避免了回溯,提升了效率。



删除

B树中的删除操作与插入操作类似,但要稍微复杂些。首先通过查找,找到关键字的位置。如果删除的关键字不在叶结点层,则先删除该节点,然后用相邻节点代替(相邻节点一定在子节点上)。这样问题就转化为删除子节点的关键字。重复上面的逻辑,如此循环,问题终于会转化为删除叶节点上的关键字。
在B树叶结点上删除一个关键字的方法是,首先将要删除的关键字直接从该叶子结点中删除。然后根据不同情况分别作相应的处理,共有三种可能情况:
(1)如果被删关键字所在结点的原关键字个数大于最小关键字数,说明删去该关键字后该结点仍满足B树的定义。这种情况最为简单,不需再做处理。
(2)如果被删关键字所在结点的关键字个数等于最小关键字数,说明删去该关键字后该结点将不满足B树的定义,需要调整。如果其左右兄弟结点中有“多余”的关键字,即与该结点相邻的右(左)兄弟结点中的关键字数目大于最小关键字数。则可将右(左)兄弟结点中最小(大)关键字上移至父亲结点。而将父亲结点中小(大)于该上移关键字的关键字下移至被删关键字所在结点中。
(3)如果左右兄弟结点中没有“多余”的关键字,即与该结点相邻的右(左)兄弟结点中的关键字数目均恰好等于最小关键字数。这种情况比较复杂。需把要删除关键字的结点与其左(或右)兄弟结点以及父亲结点中分割二者的关键字合并成一个结点(根据B树性质,可知合并后的节点,关键字树不会超过限制)。如果这个操作使父亲结点中关键字个数也变得小于最小关键字数,则从此父亲结点开始重新判断。这个过程可能一直传到根节点,根据定义,根节点至少需要有一个关键字两个子节点,所以最极端的情况下,根节点原来就只有一个关键字,此时根节点与两个子节点合并成新的根节点,导致树的高度减1。
当然也可以换个思路,查找关键字的时候就从上到下判断,如果在某节点中找到关键字,同时发现节点以及相邻子结点中,关键字数目均恰好等于最小关键字数,则先进行合并,再递归删除,这样当删除叶节点上的关键字后,就不必再次回溯,提升了效率。其中相邻子节点相互之间为兄弟节点,递归下去,与上文的思路异曲同工(另一种情况,如果某节点中没有关键字,但可以关键字在子节点中,则考虑该子节点,以及左右兄弟节点的关键字数目,与上文的思路一致)。


2-3树&&2-3-4树
B树定义中,一般规定树阶m>=2,但2阶几乎不用,所以通常可认为2-3树是最简单的B树。其实2-3树就是3阶的B树,2-3-4树就是4阶的B树,将阶数代入,就可以知道,2-3树的非叶子节点有2个或3个子节点,2-3-4树有2个或3个或4个子节点。当然,如果按照《算法导论》的定义,2-3-4树就是最简单的B树。

2-3树与红黑树的等价性

下图是红黑树转变为2-3树

2-3-4树与红黑树的等价性

下图是2-3-4树转变为红黑树

注意:一颗红黑树对应唯一形态的2-3-4树(2-3树),但是一颗2-3-4树(2-3树)可以对应多种形态的红黑树(主要死3节点那里可以对应两种不同的形态)。

B+树

B+树是B树的变体,也是一种多路搜索树。其定义基本与B树相同,除了:

1.非叶子结点的子树个数与关键字个数相同;

2.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(注意对比:B树是开区间,B+树是左开右闭区间,其实很简单,因为要容纳k[i]的值)

3.为所有叶子结点增加一个链指针;(每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+树。做这个优化的目的是为了提高区间访问的性能。)

4.所有关键字都在叶子结点出现;(父节点的关键字会出现在子节点中,且层层传递)

下图是一个3阶的B+树:

mysql索引

mysql中普遍使用B+树做索引,但在实现上又根据聚簇索引和非聚簇索引而不同。

聚簇索引是指主索引文件和数据文件为同一份文件,聚簇索引主要用在Innodb存储引擎中。对于Innodb来说,只有一个数据文件,这个数据文件本身就是用B+树形式组织,B+树每个节点的关键字就是表的主键,因此Innodb的数据文件本身就是主索引文件。主索引中的叶子页包含了数据记录,但非叶子节点只包含了主键。这种索引方式,可以提高数据访问的速度,因为索引和数据是保存在同一棵B+树之中,从聚簇索引中获取数据通常比在非聚簇索引中要来得快。这也就是为什么Innodb引擎下创建的表,必须指定主键的原因,如果没有显式指定主键,Innodb引擎仍然会对该表隐式地定义一个主键作为聚簇索引。Innodb也可以有辅助索引,但是辅助索引的数据是主键值。查找时先通过辅助索引找到主键值,然后再通过主索引去查找。


非聚簇索引的情况下,B+树叶子节点上并不是存储数据本身,而是数据存放的地址。主索引和辅助索引在实现方式上没区别,只是主索引中的key一定得是唯一的,主要用在MyISAM存储引擎中。MyISAM中的数据其实是顺序存储的,存储时分别会存在索引文件和数据文件中。查找时通过B+树可以找到数据的地址,从而获得数据。

两种索引对比

当我们修改数据库中的表的时候,数据库发生了变化,那么他们的主键的地址也就发生了变化,这样MyISAM的主索引和辅助索引就需要进行重新建立索引。而InnoDB只需要改变主索引,因为它的辅助索引是存主键的。所以这样考虑Innodb更加高效。这样的策略也减少了当出现行移动或者数据页分裂时辅助索引的维护工作。

另外,我们也就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。同时,如果主键使用的是无序的数值,例如UUID,这样在插入数据时Innodb无法简单地把新的数据插入到最后,而是需要为这条数据寻找合适的位置,这就额外增加了工作,这就是Innodb引擎写入性能要略差于MyISAM的原因之一。

B*树

B*树是B+树的变体,在B+树的非根且非叶子结点再增加指向兄弟的指针。另外,对于M阶的B*树,其非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2,由于B+树的非叶子结点的子树指针与关键字个数相同,所以B+树的关键字个数范围是(1/2)*M~M)。



参考地址:http://baike.baidu.com/

参考地址:http://www.cnblogs.com/jack204/archive/2012/08/22/2651133.html

参考地址:http://blog.csdn.net/dongdong_java/article/details/9128983

参考地址:http://blog.csdn.net/yang_yulei/article/details/26066409

参考地址:http://blog.csdn.net/flyapy/article/details/37603439

参考地址:http://www.cnblogs.com/nullzx/p/6128416.html

参考地址:http://m.blog.csdn.net/article/details?id=53164202


0 0
原创粉丝点击