数据结构~总结一下B树、B-树、B+树、B*树的特点

前言

数据在计算机中的存储结构主要为顺序存储结构、链式存储结构、索引存储结构、散列存储结构。

  • 顺序存储一般就是我们常见的数据,存储少量数据还好,如果存储大量数据可能会出现内存不够的情况,而且数据一旦需要移动和删除是很耗时的
  • 链式存储改进了上述的缺点,但是链式存储的查找效率是很低的。尤其是范围查看是非常低的
  • 散列查找是非常快的,尤其使用Hash的思想可以在几乎是O(1)的复杂度下就可以找到对应数据,但是对于范围查找还是不友好
  • 索引查找就是我们今天要讲的树+链式的结构,它使用一些树的层级特点+链式的结构,可以非常快速的找到对应的数据,而且使用链式的结完美支持了范围查找

B树(二叉查找树)

说B树前先了解一下二叉树

  • 二叉树的特点
  1. 至少有一个节点(根节点)

  2. 每个节点最多有两颗子树,即每个节点的度小于3。

  3. 左子树和右子树是有顺序的,次序不能任意颠倒。

  4. 即使树中某节点只有一棵子树,也要区分它是左子树还是右子树。
    在这里插入图片描述

而B树就是在二叉树的特定上加了一些特点。

  • 所有结点存储一个关键字;
  • 根节点的值大于其左子树中任意一个节点的值,小于其右子树中任意一节点的值,且该规则适用于树中的每一个节点。
    在这里插入图片描述
    B树的查询效率介于O(log n)~O(n)之间,理想的排序情况下查询效率为O(log n),极端情况下元素查找的效率相等于链表查询O(n)。

在这里插入图片描述
实际使用的B树都是在原B树的基础上加上平衡算法,即“平衡二叉搜索树”, 又叫做AVL树

如何保持B树结点分布均匀的平衡算法是平衡二叉树的关键;平衡算法是一种在B树中插入和删除结时的节点旋转策略。

  • AVL树的特点

具有二叉查找树的特点(左子树任一节点小于父节点,右子树任一节点大于父节点),任何一个节点的左子树与右子树都是平衡二叉树(任一节点的左右子树高度差小于1,即平衡因子为范围为[-1,1] )

  • 节点插入、旋转

AVL树插入节点的如下:

从新节点往上遍历检查每个节点的平衡因子,若发现有节点平衡因子不在[-1,1]范围内(即失衡节点u),则通过旋转重新平衡以u为根的子树

旋转的方式:

左旋转:用于平衡RR情况,对失衡节点u(unbalanced)及子树进行左旋

右旋转:用于平衡LL情况,对失衡节点u及子树进行右旋

左右旋转:用于平衡LR情况,对失衡节点失衡u的左子节点ul左旋,再对失衡节点u右旋

右左旋转:用于平衡情况,对失衡节点u失衡方向的右子节点ur右旋,再对失衡节点u左旋

  • 节点删除步骤

选择平衡,该步骤与插入区别不大,从删除节点往上遍历检查每个节点的平衡因子,若发现有节点失衡,则通过旋转重新平衡以此节点为根的子树

我们知道还有一种平衡的树叫做红黑树,我简单整理了一些对比

AVL树比红黑树更加平衡,AVL树的最大平衡差因为一定不会大于1,所有查找效率可保证是在O(logn),但是红黑树是根据节点的值与颜色维持平衡的,即可把颜色看成平衡因子,所以即使左右子树的高度差>=2也不一定像AVL树一样为了保持平衡而旋转。
所以AVL树可能在插入和删除过程中引起更多旋转造成一定时间消耗。
从节点值的角度考虑自然比红黑树更平衡,且值搜索时AVL的效率更高,但插入与删除较多时AVL树旋转操作会比红黑树更多,效率自然更慢
因此,如果应用程序涉及许多频繁的插入和删除操作,则应首选Red Black树(如 Java 1.8中的HashMap)。如果插入和删除操作的频率较低,而搜索操作的频率较高,则AVL树应优先于红黑树。

B-树

大多数自平衡搜索树(如AVL和红黑树)都会假定所有数据都在主内存中,但我们必须考虑无法容纳在主内存中的大量数据。当键的数量很大时,将以块形式从磁盘读取数据,与主存储器访问时间相比,磁盘访问时间非常高。

B-树是一种多路搜索树(并不是二叉的),设计的主要思想是减少磁盘访问次数。大多数树操作(增、删、查、最大值、最小值等)都需要都需要O(h)磁盘访问,h为树的高度。
B-树通过在节点中放置最大可能的键来保持B树的高度较低。 通常,B-树节点的大小保持与磁盘块大小相等或者是其N倍。由于B-树的高度较低,因此与B树和平衡的二叉搜索树(如AVL树、红黑树等)相比,大多数操作的磁盘访问次数显著减少。

  • B-树主要特点(M的取值主要靠设置的节点大小和所存储的键值的大小有关):

1.定义任意非叶子结点最多只有M个儿子;且M>2;

2.根结点的儿子数为[2, M];

3.除根结点以外的非叶子结点的儿子数为[M/2, M];

4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)

5.非叶子结点的关键字个数=指向儿子的指针个数-1;

6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];

7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;

8.所有叶子结点位于同一层;
在这里插入图片描述
B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;

B-树的特性:

1.关键字集合分布在整颗树中;

2.任何一个关键字出现且只出现在一个结点中;

3.搜索有可能在非叶子结点结束;

4.其搜索性能等价于在关键字全集内做一次二分查找;

5.自动层次控制;

其中,M为设定的非叶子结点最多子树个数,N为关键字总数;

所以B-树的性能总是等价于二分查找(与M值无关),也就没有B树平衡的问题;

由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占M/2的结点;删除结点时,需将两个不足M/2的兄弟结点合并,也就是说会有分页和页的操作。

所以B-树在保留二叉树预划分范围从而提升查询效率的思想的前提下,做了以下优化:

二叉树变成m叉树,这个m的大小可以根据单个页的大小做对应调整,从而使得一个页可以存储更多的数据,从磁盘中读取一个页可以读到的数据就更多,随机IO次数变少,大大提升效率。

B+树

B+树是B-树的变体,也是一种多路搜索树。

1.其定义基本与B-树同
2.非叶子结点的子树指针与关键字个数相同
3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间)
5.为所有叶子结点增加一个链指针
6.所有关键字都在叶子结点出现
在这里插入图片描述

实现动态多级索引时,通常会采用B-树和B+树的数据结构。但是,B-树有一个缺点是它将与特定键值对应的数据指针(指向包含键值的磁盘文件块的指针)以及该键值存储在B-树的节点中。该设计大大减少了可压缩到B-树节点中的条目数,从而增加了B-树中级别数与记录的搜索时间。

B+树通过仅在树的叶节点处存储数据指针来消除上述B-树的缺点,因而B+树的叶节点结构与B-树的内部节点结构完全不同。数据指针在B+树中仅存在于叶节点,因此叶节点必须将所有键值及其对应的数据指针存储到磁盘文件块以便访问。此外,叶节点也用于链接以提供对记录的有序访问。因此,叶节点才是第一级索引,而内部节点只是索引到其它级别索引的多层索引。叶节点的一些键值也出现在内部节点中,主要是作为简化搜索记录的一种媒介。

B+树与具有同级的B-树相比,具有同级的B+树可以在其内部节点中存储更多键,显着改善对任何给定关键字的搜索时间,同样的键数B+树级别较低且含指向下一个节点的指针P的存在使B+树在从磁盘访问记录时非常快速有效。如设B-树与B+树某一级别内部节点都有100K的容量,由于B-树的节点除了存键和数据指针,所以实际存的键容量连一半50K可能都没有,但B+树的100K容量都用于存键了,所以索引自然更高效。

B-树与B+树的区别

1.B-树非叶子结点和叶子结点都存储数据,因此查询数据时,时间复杂度最好为O(1),最坏为O(log n)。
B+树只在叶子结点存储数据,非叶子结点存储关键字,且不同非叶子结点的关键字可能重复,因此查询数据时,时间复杂度固定为O(log n)。

2.B+树叶子结点之间用链表相互连接,因而只需扫描叶子结点的链表就可以完成一次遍历操作,B树只能通过中序遍历。

B+树在B-树的基础上加了以下优化

1.叶子结点增加了指针进行连接,即叶子结点间形成了链表;

2.非叶子结点只存关键字key,不再存储数据,只在叶子结点存储数据;

说明:叶子之间用双向链表连接比单向链表连接多出的好处是通过链表中任一结点都可以通过往前或者往后遍历找到链表中指定的其他结点。

这样做的好处是:

1.范围查询时可以通过访问叶子节点的链表进行有序遍历,而不再需要中序回溯访问结点。

2.非叶子结点只存储关键字key,一方面这种结构相当于划分出了更多的范围,加快了查询速度,另一方面相当于单个索引值大小变小,同一个页可以存储更多的关键字,读取单个页就可以得到更多的关键字,可检索的范围变大了,相对IO读写次数就降低了。

为什么 B+ 树比 B-树更适合应用于数据库索引?

B+树更加适应磁盘的特性,相比B树减少了I/O读写的次数。由于索引文件很大因此索引文件存储在磁盘上,B+树的非叶子结点只存关键字不存数据,因而单个页可以存储更多的关键字,即一次性读入内存的需要查找的关键字也就越多,磁盘的随机I/O读取次数相对就减少了。

B+树的查询效率相比B树更加稳定,由于数据只存在在叶子结点上,所以查找效率固定为O(log n)。

B+树叶子结点之间用链表有序连接,所以扫描全部数据只需扫描一遍叶子结点,利于扫库和范围查询;B树由于非叶子结点也存数据,所以只能通过中序遍历按序来扫。也就是说,对于范围查询和有序遍历而言,B+树的效率更高。

B*树

是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针

在这里插入图片描述
B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);

  • B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;

  • B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;

B*树在进行页分裂和合并的时候会优先考虑兄弟节点是不是还没有满,所以分配新结点的概率比B+树要低,所以空间使用率更高;

猜你喜欢

转载自blog.csdn.net/Shangxingya/article/details/114916278
今日推荐