B树的创建就是为了优化数据库查找,如果采用二叉查找树(时间复杂度只要logN)来进行查找,那么在磁盘进行I/O操作时,(数据太大需要进行分页)每个磁盘页对应一个节点;最坏情况:查找次数等于输的高度(时间复杂度logN)。例如:
自顶向下查找10:需要4次
那这样的话,通过将树变成矮树胖树即可,B树的结构:
每个节点不超过磁盘页的大小(这里是3阶B树,每个磁盘页大小是3)即可;
每个节点的结构:
B+树是B树的变种:
一、B树
B树也称B-树,它是一颗多路平衡查找树。二叉树我想大家都不陌生,其实,B树和后面讲到的B+树也是从最简单的二叉树变换而来的。
1.1 一个M阶的B树具有如下几个特征:
- 任意非叶子结点最多只有M个儿子,且M>2;
- 根结点的儿子数为[2, M];
- 除根结点以外的非叶子结点的儿子数为[M/2, M],向上取整;
- 非叶子结点的关键字个数=儿子数-1;
- 所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同;
- k个关键字把节点拆成k+1段,分别指向k+1个儿子,同时满足查找树的大小关系
- 每个节点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它;
- 每个节点都存有索引和数据,也就是对应的key和value。
1.2 有关B树的一些特性,注意与后面的B+树区分
- 关键字集合分布在整颗树中;
- 任何一个关键字出现且只出现在一个结点中;
- 搜索有可能在非叶子结点结束;
- 其搜索性能等价于在关键字全集内做一次二分查找;
1.3 B树的查找
如图是一颗3阶B树,查询元素5的过程如下:
(1)第一次磁盘IO,把9所在节点读到内存,把目标数5和9比较,小,找小于9对应的节点;
(2)第二次磁盘IO,还是读节点到内存,在内存中把5依次和2、6比较,定位到2、6中间区域对应的节点;
(3)第三次磁盘IO就不上图了,跟第二步一样,然后就找到了目标5。
可以看到,B树在查询时的比较次数并不比二叉树少,尤其是节点中的数非常多时,但是内存的比较速度非常快,耗时可以忽略,所以只要树的高度低,IO少,就可以提高查询性能,这是B树的优势之一。
1.4 B树的插入
比如我们要在下图中插入元素4:
(1)首先自顶向下查询找到4应该在的位置,即3、5之间;
(2)但是3阶B树的节点最多只能有2个元素,所以把3、4、5里面的中间元素4上移(中间元素上移是插入操作的关键);
(3)上一层节点加入4之后也超载了,继续中间元素上移的操作,现在根节点变成了4、9;
(4)还要满足查找树的性质,所以对元素进行调整以满足大小关系,始终维持多路平衡也是B树的优势,最后变成这样:
1.5 B树的删除
比如我们要删除元素11:
(1)自顶向下查询到11,删掉它;
(2)然后不满足B树的条件了,因为元素12所在的节点只有一个孩子了,所以我们要“左旋”,元素12下来,元素13上去:
这时如果再删除15呢?很简单,当元素个数太少以至于不能再旋转时,12直接上去就行了。
二、B+树
B+树,是B树的一种变体,查询性能更好。
2.1 一个M阶的B+树具有如下几个特征:
- 有n棵子树的非叶子结点中含有n个关键字(B树是n-1个),这些关键字不保存数据,只用来索引,所有数据都保存在叶子节点(B树是每个关键字都保存数据);
- 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接;
- 所有的非叶子结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字;
- 通常在B+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点;
- 同一个数字会在不同节点中重复出现,根节点的最大元素就是B+树的最大元素。
三、B+树相比于B树的查询优势
- B+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更“矮胖”;
- B+树查询必须查找到叶子节点,B树只要匹配到即可不用管元素位置,因此B+树查找更稳定(并不慢);
- 对于范围查找来说,B+树只需遍历叶子节点链表即可,B树却需要重复地中序遍历,如下两图: