数据库索引 - B树

目录

1. 前言

1.1 B树、B+树出现原因?

1.2 一种新的查找树结构——多路查找树

2. 多路查找树 —— 平衡树—— B树

查询

插入

删除

3、B+树



1. 前言

参考:https://blog.csdn.net/z_ryan/article/details/79685072

 

二叉查找树的查找时间复杂度为O(log(2,N)),其查找效率已经足够高了,那为什么还有B树和B+树的出现呢?难道它两的时间复杂度比二叉查找树还小吗?

IO操作的效率很低,那么,当在大量数据存储中,查询时我们不能一下子将所有数据加载到内存中,只能逐一加载磁盘页,每个磁盘页对应树的节点。造成大量磁盘IO操作(最坏情况下为树的高度)。平衡二叉树由于树深度过大而造成磁盘IO读写过于频繁,进而导致效率低下。


1.1 B树、B+树出现原因?

原因:平衡二叉树是”瘦高“的,即其树的深度相对比较深,而深度越深,会造成读写数据时候磁盘IO的读写过于频繁,进而效率低下。(逻辑环:树深度深 —— 磁盘IO读写频繁 —— 效率低下)。因此,需要一种新的查找树结构,他应该满足:

降低树的深度(让树变得”矮胖“),从而减少磁盘IO读写的次数,提高效率。

 


1.2 一种新的查找树结构——多路查找树

思路

1. 每个节点存储多个元素;

2. 采用多叉结构。

根据AVL给我们的启发,一颗平衡多路查找树(B树,Balance Tree)自然可以使得数据的查找效率保证在O(logN)这样的对数级别上。


2. 多路查找树 —— 平衡树—— B树

一个m阶的B树具有如下几个特征:

B树中所有结点的孩子结点最大值称为B树的阶,通常用m表示。

一个结点有k个孩子时,必有k-1个关键字才能将子树中所有关键字划分为k个子集。

1.根结点至少有两个子女;
2.每个中间节点都包含k-1个元素和k个孩子,其中 ceil(m/2) ≤ k ≤ m;
3.每一个叶子节点都包含k-1个元素,其中 ceil(m/2) ≤ k ≤ m;
4.所有的叶子结点都位于同一层;
5.每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域划分;
6.每个结点的结构为:(n,A0,K1,A1,K2,A2,…  ,Kn,An). 其中,Ki(1≤i≤n)为关键字,且Ki<Ki+1(1≤i≤n-1)。
Ai(0≤i≤n)为指向子树根结点的指针。且Ai所指子树所有结点中的关键字均小于Ki+1;
7.n为结点中关键字的个数,满足ceil(m/2)-1≤n≤m-1。

示例:三阶B树(实际中节点中元素很多)

这里写图片描述

 


查询


  以上图为例:若查询的数值为5:
  第一次磁盘IO:在内存中定位(与17、35比较),比17小,左子树;
  第二次磁盘IO:在内存中定位(与8、12比较),比8小,左子树;
  第三次磁盘IO:在内存中定位(与3、5比较),找到5,终止。


整个过程中,我们可以看出:比较的次数并不比二叉查找树少,尤其适当某一节点中的数据很多时,但是磁盘IO的次数却是大大减少。比较是在内存中进行的,相比于磁盘IO的速度,比较的耗时几乎可以忽略。所以当树的高度足够低的话,就可以极大的提高效率。相比之下,节点中的元素多点也没关系,仅仅是多了几次内存交互而已,只要不超过磁盘页的大小即可。


插入


  对高度为k的m阶B树,新结点一般是插在叶子层。通过检索可以确定关键码应插入的结点位置。然后分两种情况讨论:
  1、 若该结点中关键码个数小于m-1,则直接插入即可。
  2、 若该结点中关键码个数等于m-1,则将引起结点的分裂。以中间关键码为界将结点一分为二,产生一个新结点,并把中间关键码插入到父结点(k-1层)中
  重复上述工作,最坏情况一直分裂到根结点,建立一个新的根结点,整个B树增加一层。

例如:在下面的B树中插入key:4

这里写图片描述

第一步:检索key插入的节点位置如上图所示,在3,5之间;

第二步:判断节点中的关键码个数:
  节点3,5已经是两元素节点,无法再增加。父亲节点 2, 6 也是两元素节点,也无法再增加。根节点9是单元素节点,可以升级为两元素节点。;

第三步:结点分裂:
  拆分节点3,5与节点2,6,让根节点9升级为两元素节点4,9。节点6独立为根节点的第二个孩子。

最终结果如下图:虽然插入比较麻烦,但是这也能确保B树是一个自平衡的树.

这里写图片描述


 


删除


  B树中关键字的删除比插入更复杂,在这里,只介绍其中的一种方法:
  
  在B树上删除关键字k的过程分两步完成:

   (1)找出该关键字所在的结点。然后根据 k所在结点是否为叶子结点有不同的处理方法。
   (2)若该结点为非叶结点,且被删关键字为该结点中第i个关键字key[i],则可从指针son[i]所指的子树中

找出最小关键字Y,代替key[i]的位置,然后在叶结点中删去Y。
因此,把在非叶结点删除关键字k的问题就变成了删除叶子结点中的关键字的问题了。

在B-树叶结点上删除一个关键字的方法:
  首先将要删除的关键字 k直接从该叶子结点中删除。然后根据不同情况分别作相应的处理,共有三种可能情况:

  • (1)如果被删关键字所在结点的原关键字个数n>=ceil(m/2),说明删去该关键字后该结点仍满足B树的定义。

这种情况最为简单,只需从该结点中直接删去关键字即可。

  • (2)如果被删关键字所在结点的关键字个数n等于ceil(m/2)-1,说明删去该关键字后该结点将不满足B树的定义,

需要调整。调整过程为:
       如果其左右兄弟结点中有“多余”的关键字,即与该结点相邻的右(左)兄弟结点中的关键字数目大于
ceil(m/2)-1。则可将右(左)兄弟结点中最小(大)关键字上移至双亲结点。而将双亲结点中小(大)于该上
移关键字的关键字下移至被删关键字所在结点中。
       如果左右兄弟结点中没有“多余”的关键字,即与该结点相邻的右(左)兄弟结点中的关键字数目均等于
ceil(m/2)-1。这种情况比较复杂。需把要删除关键字的结点与其左(或右)兄弟结点以及双亲结点中分割二者
的关键字合并成一个结点,即在删除关键字后,该结点中剩余的关键字加指针,加上双亲结点中的关键字Ki一起,
合并到Ai(是双亲结点指向该删除关键字结点的左(右)兄弟结点的指针)所指的兄弟结点中去。如果因此使双亲
结点中关键字个数小于ceil(m/2)-1,则对此双亲结点做同样处理。以致于可能直到对根结点做这样的处理而使
整个树减少一层。

总之,设所删关键字为非终端结点中的Ki,则可以指针Ai所指子树中的最小关键字Y代替Ki,然后在相应结点中删除Y。对任意关键字的删除都可以转化为对最下层关键字的删除。

下面举一个简单的例子:删除元素11.

这里写图片描述


第一步:判断该元素是否在叶子结点上。
   该元素在叶子节点上,可以直接删去,但是删除之后,中间节点12只有一个孩子,不符合B树的定义:每个中间节点都包含k个孩子,(其中 ceil(m/2) <= k <= m)所以需要调整;

第二步:判断其左右兄弟结点中有“多余”的关键字,即:原关键字个数n>=ceil(m/2) - 1;
  显然结点11的右兄弟节点中有多余的关键字。那么可将右兄弟结点中最小关键字上移至双亲结点。而将双亲结点中小于该上移关键字的关键字下移至被删关键字所在结点中即可

注意
  ①、B树主要用于文件系统以及部分数据库索引,例如: MongoDB。而大部分关系数据库则使用B+树做索引,例如:mysql数据库;
  ②、从查找效率考虑一般要求B树的阶数m >= 3;
  ③、B-树上算法的执行时间主要由读、写磁盘的次数来决定,故一次I/O操作应读写尽可能多的信息。因此B-树的结点规模一般以一个磁盘页为单位。一个结点包含的关键字及其孩子个数取决于磁盘页的大小。
 

 


3、B+树

 

篇幅有限,另起文章

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值