关于我的 Leetcode 题目解答,代码前往 Github:https://github.com/chenxiangcyr/leetcode-answers
B树是一种查找树,目的都是为了解决某种系统中,查找效率低的问题。
二叉查找树的特点是每个非叶节点都只有两个孩子节点。然而这种做法会导致当数据量非常大时,二叉查找树的深度过深,搜索算法自根节点向下搜索时,需要访问的节点也就变的相当多。
如果这些节点存储在外存储器磁盘中,每访问一个节点,相当于就是进行了一次I/O操作,随着树高度的增加,频繁的I/O操作一定会降低查询的效率。
B树的基本逻辑就是这个思路,它要改二叉为多叉,每个节点存储更多的指针信息,以降低I/O操作数。
B 树
一颗 m 阶的B树是一颗平衡的 m 路搜索树,它或者为空树,或者满足下列条件:
- 每个结点最多有 m 个孩子
- 除根结点外,每个非叶子结点至少有
[ceil(m/2)]
个孩子结点 - 若根节点不是叶子结点,则它至少有 2 个孩子
- 有 k 个孩子的非叶子结点有 k-1 个关键码,关键码按递增次序排列
- 所有的叶子结点都在同一层。B树的叶子结点可以看作一种外部结点,不包含任何信息
一个标准的 B树 如下图:
有关B树的一些特性:
- 关键字集合分布在整颗树中;
- 任何一个关键字出现且只出现在一个结点中;
- 搜索有可能在非叶子结点结束;
- 其搜索性能等价于在关键字全集内做一次二分查找;
B树的高度
所以当B树包含 N 个关键字时,B树的最大高度为 K-1(因为计算B树高度时,叶结点所在层不计算在内),即:K - 1 = log┌m/2┐((N+1)/2 )+1
。
在搜索B树时,很明显,访问节点(即读取磁盘)的次数与树的高度呈正比,而B树与平衡的或者普通的二叉查找树相比,虽然高度都是对数数量级,但是显然B树中 log
函数的底可以比 2 更大,因此,和二叉树相比,极大地减少了磁盘读取的次数。
B树的搜索
搜索关键字的29的文件的过程:
- 从根节点开始,读取根节点信息,根节点有2个关键字:17和35。因为17 < 29 < 35,所以找到指针P2指向的子树,也就是磁盘块3(1次I/0操作)
- 读取当前节点信息,当前节点有2个关键字:26和30。26 < 29 < 30,找到指针P2指向的子树,也就是磁盘块8(2次I/0操作)
- 读取当前节点信息,当前节点有2个关键字:28和29。找到了!(3次I/0操作)
B树的插入
首先找到要插入的关键字应该插入的叶子节点 u
。如果 u
是满的,那么由于不能将一个关键字插入满的节点,我们需要对 u
按其当前排在中间关键字u.keyt
进行分裂,分裂成两个节点 u1
,u2
;同时,作为分裂标准的关键字 u.keyt
会被上移到 u
的父节点中,在 u.keyt
插入前,如果 u
的父节点未满,则直接插入即可;如果 u
的父节点已满,则按照上面的方法对u的父节点分裂,这个过程如果一直不停止的话,最终会导致B树的根节点分裂,B树的高度增加一层。
B树的删除
删除操作的基本思想和插入操作是一样的,都是不能因为关键字的改变而改变B树的结构。
插入操作主要防止的是某个节点中关键字的个数太多,所以采用了分裂;删除则是要防止某个节点中,因删除了关键字而导致这个节点的关键字个数太少,所以采用了合并操作。
- 如果在当前节点中,找到了要删的关键字,且当前节点为内部节点。那么,如果有比较丰满的前驱或后继,借一个上来,再把要删的关键字降下去,在子树中递归删除;如果没有比较丰满的前驱或后继,则令前驱与后继合并,把要删的关键字降下去,递归删除;
- 如果在当前节点中,还未找到要删的关键字,且当前节点为内部节点。那么去找下一步应该扫描的孩子,并判断这个孩子是否丰满,如果丰满,继续扫描;如果不丰满,则看其有无丰满的兄弟,有的话,从父亲那里接一个,父亲再找其最丰满的兄弟借一个;如果没有丰满的兄弟,则合并,再令父亲下降,以保证B树的结构。
B+树
B+树是B树的一种变形,它更适合实际应用中操作系统的文件索引和数据库索引。
m 阶的B+树的特征:
- 有 n 棵子树的非叶子结点中含有 n 个关键字(B树是n-1个),这些关键字不保存数据,只用来索引,所有数据都保存在叶子节点(B树是每个关键字都保存数据)。
- 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
- 所有的非叶子结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。
- 通常在B+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点。
- 同一个数字会在不同节点中重复出现,根节点的最大元素就是B+树的最大元素。
一个标准的 B+树 如下图:
B树 Vs B+树
B+树和B树相比,主要的不同点在以下2项:
- 内部节点中,关键字的个数与其子树的个数相同,不像B树种,子树的个数总比关键字个数多1个
- 所有指向文件的关键字及其指针都在叶子节点中,不像B树,有的指向文件的关键字是在内部节点中。换句话说,B+树中,内部节点仅仅起到索引的作用,在搜索过程中,如果查询和内部节点的关键字一致,那么搜索过程不停止,而是继续向下搜索这个分支。
根据B+树的结构,我们可以发现B+树相比于B树,在文件系统,数据库系统当中,更有优势,原因如下:
B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说I/O读写次数也就降低了。B+树的查询效率更加稳定
由于内部结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。B+树更有利于对数据库的扫描
B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题,而B+树只需要遍历叶子节点就可以解决对全部关键字信息的扫描,所以对于数据库中频繁使用的 range query,B+树有着更高的性能。
引用:
B树与B+树
平衡二叉树、B树、B+树、B*树 理解其中一种你就都明白了
B树(B-树)、B+树、AVL树、B*树
b树和b+树的区别