赞
踩
B 树是为了磁盘或其它存储设备而设计的一种多叉平衡查找树,多叉就是多个分支的意思,二叉树就是最多只有两个分支的树。如下图所示,即是一棵 B树。
一棵 m 阶的 B 树必须满足如下条件:
1)每个结点最多含有 m 个分支,也就是说:每个节点最多 m−1 个关键字。
2)根节点最少可以有 1 个关键字,其它节点最少有 ⌈m2⌉−1 个关键字。
3)每个节点的内部结构为:n 为节点中关键字的个数,Ki,i=1,2,...,n 为关键字,从小到大排列,Pi,i=0,1,...,n为指向关键字满足
[Ki,Ki+1]范围的孩子节点。
这里认为上面的 B 树高度为 3,第三层就是叶子节点。至于有些材料说那些 null 是叶子节点,上述B树为4层,简直扯淡。
这里,我们再结合数据库索引说一下B树的用途,例如在SQLite中,使用了B树和B+树,如果key只是一个int值(比如主键,而不是整条数据库中的一条记录),那么将int值作为key存到B树中,会比存到B+树中更高效,因为B树的中间节点已经附带了key,而这些key是不需要在叶子节点中再保存一份的,也就是叶子结点和内部节点没有key的交集。但是如果把record也都放到了节点中,则B树就不如B+树快了,因为SQLite为了加快搜索,一个节点一般都是物理磁盘页的整数倍,所以在相同的node占用空间大小的情况下,B树的一个node存放的item更少(因为record肯定比4字节大),造成树更高,而B+ 树的一个节点就会存放更多item,树更矮,查找效率更高。
B 树的节点类型定义如下,这个定义只是用来查找内存数据的(也就是逻辑结构,主要是为了讲解和程序中快速获取对应的字段,并不是磁盘上的存储结构),如果用来查找外存,代码需要调整一下,下面叙述。
1 2 3 4 5 6 7 8 |
|
B 树设计的目的是用来查找磁盘的,为了简单,假设每个盘块正好存放一个 B树的结点,这里用少量数据构造一棵 3 叉树的形式,来描述文件查找的具体过程(下面这个B树是一个磁盘文件索引树,操作系统一般都提供了对某个盘的建立索引的功能)。
上面的图中比如根结点,其中 17 表示一个磁盘文件的文件名(是一个string类型,而不是int);小红方块表示这个 17 文件内容在硬盘中的存储位置;P1表示指向 17 左子树的指针。
此时节点类型定义如下:
1 2 3 4 5 6 7 8 9 |
|
下面来模拟下查找文件 29 的过程:
1)根据根结点指针找到文件目录的根磁盘块 1,将其中的信息导入内存。【磁盘 IO 操作 1 次】
2)此时内存中有两个文件名 17、35 和三个存储其他磁盘页面地址的数据。根据算法我们发现:17<29<35,因此我们找到指针 P2。
3)根据 P2 指针,我们定位到磁盘块 3,并将其中的信息导入内存。【磁盘 IO 操作 2 次】
4)此时内存中有两个文件名 26、30 和三个存储其他磁盘页面地址的数据。根据算法我们发现:26<29<30,因此我们找到指针 P2。
5)根据 P2 指针,我们定位到磁盘块 8,并将其中的信息导入内存。【磁盘 IO 操作 3 次】
6)此时内存中有两个文件名 28、29。根据算法我们查找到文件名 29,并定位了该文件内存的磁盘地址。
分析上面的过程,发现需要 3 次磁盘 IO 操作和 3 次内存查找操作。关于内存中的文件名查找,由于是一个有序表结构,可以利用折半查找提高效率。
至于 IO操作是影响整个 B 树查找效率的决定因素。
根据上面的例子我们可以看出,对于辅存做 IO 读的次数取决于 B 树的高度。而 B 树的高度由什么决定的呢?
问题:一棵 m 阶 B树,关键字个数为 n,求高度 h(不包含叶子节点)的取值范围。
无论是兄弟够借还是兄弟不够借,都要让父节点的一个关键字下来,如果兄弟够借,则让兄弟还父节点一个关键字,否则就不还了, 并且将自身、兄弟节点与借下来的关键字合并。如果没有还父节点一个关键字,此时需要递归判断父节点是否违背了条件。若违反条件则继续让祖父节点中的关键字先来,然后又是兄弟够不够借的问题。
b. 当被删除的关键字不在终端节点:用需要被删除关键字的前驱关键字或后继关键字替换后,会变成情况 1。
找前驱:就是从关键字 k 所在节点的左子树一直往右找。找后继:从关键字 k 所在节点的右子树一直往左找。举个例子:
如下图,要删除的项在非叶子节点上。比如删除关键字 M。
找到对应的中序前驱,从关键字 M 左边的指针指向的左子树开始一直往右走,最终可以找到前驱为 L,将 L,M做个交换,此时 M 在叶子节点上,删除 M,如上右图。删除之后树不平衡,但发现兄弟节点可以借项给它,按照情况 1 进行调整即可,调整后如下图:
B+ 树:是应文件系统所需而产生的一种 B 树的变形树。一棵 m 阶的 B+ 树和 m阶的 B 树的异同点在于:
1)每个节点分支数和 B 树一致。
2)B+ 树节点内关键字的个数就等于该节点的分支数,而不是如 B 树那样减 1。
3)B+ 树改进了 B 树, 让内结点只作索引使用, 去掉了其中指向 data record 的指针, 使得每个结点中能够存放更多的 key, 因此能有更大的出度。
下面左图是 B 树,右图是 B+ 树。data 就是文件地址。
4)所有父节点的元素都同时存在于子节点,是子节点中是最大(或最小)元素。举个例子:
在上面这棵树中,根节点的元素 8 是节点 2,5,8 和 节点 6,8的最大元素,15 也是一样的。
5)由于 4),所以 B+ 树的叶子节点会包含全部的关键字信息,并且每个叶子节点包含指向相应记录的指针,所有叶子节点形成一个链表。
为什么说 B+ 树比 B 树更适合实际应用中操作系统的文件索引和数据库索引?
1)B+树的磁盘读写代价更低:B+ 树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对 B 树更小。如果把所有同一内 部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO 读写次数也就降低了。
2)B+ 树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。