数据库索引一二事(一)--B树结构

数据库的索引的底层实现就是B+树,要学习它,需要先学习B树是个什么鬼东西。首先声明,对于B树的理解我也只是尽我所能的去解释和去学习,有些地方的说法估计不专业或者不准确,大家凑合着看。

关于前面的长篇大论我不想再这边定义,因为东西太多,包括B树里面也有很多知识点,我只挑现阶段我想知道的,因为想为后续索引的实践做基础。

B树的定义:

B 树又叫平衡多路查找树。一棵m阶的B 树(意味着某一个节点最多有m个字树,但这并不意味着m阶树就叫m叉树,这个要注意)。

1.树中每个结点最多含有m个孩子(m>=2);

2.除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]个孩子(其中ceil(x)是一个取上限的函数);

3.若根结点不是叶子结点,则至少有2个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点);

4.所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(这一点解释的有疑问,暂时不关注

5.每个非终端结点中包含有n个关键字信息: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:
    a)   Ki (i=1...n)为关键字,且关键字按顺序升序排序K(i-1)< Ki。 
    b)   Pi为指向子树根的接点,且指针P(i-1)指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)。 
    c)   关键字的个数n必须满足: [ceil(m / 2)-1]<= n <= m-1。下图为网络盗图一张,不要在意这些细节....


基于上述图片的层级结构,如果我们需要查询数据是29的数据时,发生了些什么呢?

1.找到磁盘块1,加载到内存中(IO一次)。

2.查到在磁盘块3上,加载磁盘块3到内存(IO一次)。

3.查到在磁盘块8上,加载到内存中(IO一次)。

总共3此IO操作就查询到了数据,如果没有这个结构,那么最差的情况需要多少次呢?估计需要O(n)此IO操作吧,这就是B树结构的好处,查询次数是指数级别的降低。

B树的插入:

假设现在我们需要插入的B树规定是m阶,那么需要关注几个数值

定义第2点:至少存在Ceil[(5/2)] = 3个孩子。

定义第5点:关键字Ceil[(5/2)]  - 1 <= n <= 5-1 ---> 2 <= n <=4;就是关键字最少是2个,最多是4个,超过就需要分裂,少于2个就需要合并。

假设插入的数据 是C N G A H E K Q M F W L T Z D P R X Y S

1.首次插入4个数据C,N,G,A,因为关键字最大是4个,假设第一次一次性插入4个数据。


2:

2.1当插入H时,发现次数关键字数量超过4个了,需要执行分裂的操作,插入前状态如下


2.2插入后状态如下:


2.3 分裂过程如下:

1. 生成一新结点。

2. 把原结点上的关键字和k按升序排序后,从中间位置把关键字(不包括中间位置的关键字)分成两部分。左部分所含关键字放在旧结点中,右部分所含关键字放在新结点中,中间位置的关键字连同新结点的存储位置插入到父结点中。

3. 如果父结点的关键字个数也超过(m-1),则要再分裂,再往上插。直至这个过程传到根结点为止。    


3.插入E,K,Q时不需要分裂


4.插入M时需要分裂一次了,因为此时右边的节点的关键字已经到4个。


5.插入F,W,L,T不需要分裂操作


6.插入Z时,最右边的需要分裂,这里就直接贴结果,不分析了,具体分析上面已经有了


7.插入D,导致最左边的需要分裂,P,R,X,Y插入没什么事情


8.最后插入S时,我们来看下分裂过程,注意上图中,S应该插入的位置在哪儿呢?根节点上M-->T指向的是第四个孩子节点,第五个孩子节点上都是大于T的字母,因此S应该是插入到第四个节点上去。在分裂过程2中上移Q节点的时候需要注意下,在分裂DGMTQ时需要先进行排序,最终结果是DGMQT,然后在将M节点分裂,向上操作。


B树的删除:

完善一下插入的时候的最终一幅图如下,画的好看些。删除的时候也需要关注的一个最重要的点就是关键字个数,需要保证n在[2,4]之间,也就是Ceil[5/2]-1 <= n <= m-1(B树的插入里面讲的)


1.一个一个看,基于图1假设我们先删除H,可以看见H是在叶子节点中,并且叶子节点的关键字是3>2,所以它可以直接删除,然后K和L前移就ok了。见图2


2.基于图1假设我们要删除T,T是在非叶子节点中,对于非叶子节点中的关键字K,需要使用其中序前驱(后继)关键字K'代替K,然后从叶子节点中删除K'。(注意此处可以直接删除叶子节点中的K'也就是W,是因为叶子节点的数目是4>2)此处暂时先根据图1的情况进行分析,最后进行情况汇总,先有个整体的思想。见图3.


3.基于图3,我们删除R关键字,我们发现R在叶子节点中,并且此叶子节点的关键字数目是2 = 2,因此如果直接删除的话就会破坏掉B树的结构,所以需要“借”,怎么借是关键,“借”的方法很简单,看它兄弟姐妹有没有,观察“NP”和“XYZ”两个兄弟节点,发现在其右边的兄弟XYZ”的关键字数目是3>2,因此它时有富余的,可以借一个过来。而且借的这一个在右边节点中,因此是借最左边一个(因为选择右边节点的话最小的关键字是在最左边,这样移动不会破坏B树的顺序),见图4--1和图4--2.


4.情况3还有一种情况是删除R时发现左边节点的数据满足要求,(所以在选择左边节点时需要移动最右边的关键字)进行翻转(我自己定义的名词,我觉得这部分和红黑树有点像像)。见图5--1和图5--2.需要注意的是R和S的关系,B树的特点就是左边永远小于右边,所以有点地方会存在左右移动的情况。


5.

5.1 重头戏来了,假设现在基于图5--2我们删除关键字E,会出现什么结果呢?至于为什么出是这么移动,在文章的最后我会贴出移动规则,请先跟着例子走,思考一下^_^。


5.2 此时结果就是图6--2,但是呢,G节点只有一个关键字G,并不满足[2,4]的要求,所以这个时候还是需要继续移动,以满足B树的要求。我们来看怎么移动,详情见上面步骤3的图4--1,可以想借的办法,但是节点G的兄弟节点,“PW”刚好满足最小值2,所以它借不了,借不了,那只能是合并了,因此需要合并G和PW,合并的时候操作见步骤5.1,需要下移父亲节点M。


5.3,步骤5.2里面需要合并的原因是兄弟节点不能借,我们来看看能借的情况下是怎么移动的?假设步骤5.1中移动完图6--1之后的情况如下图6--5.结果是图6--6,这个时候由于合并了ACDF,导致了G节点的关键字只有1 < 2,同时又发现兄弟节点“PSW”的关键字是3>2,可以借一个过来,借右边的关键字的话,要拿最左边的关键P,所以P上移,M下移到G节点中,但是此时和步骤4的图5--1不同的是,M和P关键字之间是存在节点NO的,因此需要移动NO跟在M后面才行,详情见图6--6。这一例子是借右边节点,同理可以模拟借左边节点,一样的道理。


B树的总结:

总结很重要,上面的例子都是一步一步的在探索,

B树的插入:对于上面插入的过程,我们可以看见,有的是直接插入,有的需要进行“分裂”,但是我们注意到对于非叶子节点,我们没有直接插入数据,这些非叶子节点都是分裂出来的,根节点也同样,如果只有一个元素,那它就是根节点。

1.插入一个元素时,首先检查是否存在,如果不存在,则在叶子节点插入该数据(需要注意的是在插入过程中可能产生关键字的右移)。

2.如果叶子节点的空间满了,也就是关键字数目条件不满足时(Ceil[m/2]-1 <=n <= m-1),需要进行分裂操作,操作的原则是将此时的关键字分成两半,各形成两个新的节点,而中间节点则上升成为父节点(同样的,如果此时父节点也满了,也需要进行分裂)。

3.在分裂过程中伴随着还有对应指针的移动,通过关键字可以看出左指针指向的数据比右指针指向的数据小。

4.如果根节点满了的话,会造成根节点关键字的上移,树的高度增加一层。

B树的删除:删除操作总的来说比较繁琐,繁琐的地方在于要维护B树的特性。怎么维护B树特性,两个重要的方法是“合并”节点和“借”关键字。依据的原则依然是关键字数目(Ceil[m/2]-1 <=n <= m-1)。

为了能够简化理解,我打算将删除的节点分成叶子节点和非叶子节点总结,这样能够清晰的看到删除情况。

1.如果删除的是叶子节点上的数据,删除之后移动元素保证该叶子节点顺序不变。

    1.1 如果此时关键字个数依然满足条件,则删除结束。

    1.2 如果此时关键字个数小于条件个数,则需要进行移动。

            1.2.1首先看其相邻兄弟节点是否有富余关键字,如果有,则将两个节点中间的父节点的关键字下移到当前要删除节点内,注意此处放入的时候需要保持顺序,然后将富余节点的最左(最右)关键字上移到父节点中,然后删除该关键字。(此处需要注意图6--5的情况)

            1.2.2如果其相邻兄弟节点没有富余关键字了,则需要将该节点和相邻节点进行合并(选择左右没关系,但是要保证顺序的一致就行),移动的规则是将父节点(父节点必须在两个需要合并的节点之间)下移到改节点中被删除的关键字处,然后记性合并操作,操作之后可能父节点能满足数目条件,如果不满足的话,仍然再次执行1.2.1 --> 1.2.2的步骤,简要的说就是先看相邻兄弟节点是否富余,富余的话借父节点,不富余的话就合并。

2.如果删除的是非叶子节点上的数据,非叶子节点特殊性在于它存在孩子节点。

    2.1 首先将该关键字的后继节点中的最左边上移到该位置(这个最左边是指中序遍历后继节点中最左边的节点),如果后继节点上移一个关键字之后满足数目条件,则结束。

    2.2 后继节点数目不满足条件数目了,则需要观察能否进行借的操作,如果不能借,则需要合并,和叶子节点操作类似。

在此引用一段话,实践下来说的很对。

1)删除操作的两个步骤
   
  第一步骤:在树中查找被删关键字K所在的地点
    
 第二步骤:进行删去K的操作

2)删去K的操作
    
 B-树是二叉排序树的推广,中序遍历B-树同样可得到关键字的有序序列(具体遍历算法【参见练习】)。任一关键字K的中序前趋(后继)必是K的左子树(右子树)中最右()下的结点中最后(最前)一个关键字。

加大加红的这句话需要细细品味。

最后再贴一个删除非叶子节点的场景的小练习吧,需要删除的是120这个节点


删除当中主要的步骤,细细去读下,不是很复杂的



猜你喜欢

转载自blog.csdn.net/qq_32924343/article/details/80167184