B树学习----查询 插入 删除

参考算法导论第三版

1.B树的定义

任何和关键字相联系的“卫星数据”将于关键字一样存放在同一个节点中。

一棵B树T是具有以下性质的有根树(根为T.root):

1.每个节点x都有下面属性:

a. x.n, 当前存储在节点x中的关键字个数。

b. x.n, n个关键字本身x.key1, x.key2, …, x.keyz, …, x.keyx.n, 以非降序存放,是的x.key1 <= x.key2 <= … <= x.keyx.n.

c. x.leaf, 一个bool值,如果x是叶节点,则为TRUE;如果x为内部节点,则为FALSE.

2.每个内部节点x还包含x.n+1个指向其孩子的指针x.c1, x.c2, … , x.cx.n+1。叶节点没有孩子,所以它们的ci属性没有定义。

3.关键字x.keyi对存储在各子树种的关键范围加以分割:如果ki为任意一个存储在以x.ci为根的子树中的关键字,那么

    《B树学习----查询 插入 删除》

4.每个叶节点具有相同的深度,即树的高度h。

5.每个节点所包含的关键字个数有上界和下界。用一个被称为最小度数(minmum degree)的固定整数 t>=2来表示这些界。

a.除了根节点以外的每个节点必须至少有t-1个关键字。因此,除了根节点以外的每个节点至少有t个孩子。如果树非空,根节点至少有一个关键字。

b.每个节点至多可包含2t-1个关键字。因此,一个内部节点至多可有2t个孩子。当一个节点恰好有2t-1个关键字是,则称该节点是满的(full)。

t = 2时的B树是最简单的。每个内部节点有2个、3个或4个孩子,即一棵2-3-4树。然而在实际中,t的值越大,B树的高度就越小。

B树的高度

B树上大部分的操作所需的磁盘存取次数与B树的高度是成正比的。现在来分析B树最坏情况下的高度。

定理18.1 如果 n >= 1,那么对任意一棵包含n个关键字、高度为h、最小读书t>=2的B树T来说,有

                                      《B树学习----查询 插入 删除》  

搜索B树

《B树学习----查询 插入 删除》  

创建一棵空的B树

      《B树学习----查询 插入 删除》

向B树种插入一个关键字

将一个满的节点y(有2t-1个关键字)按其中间关键字(median key) y.keyt分裂成两个各含t-1个关键字的节点。中间节点被提升到y的父节点,以标识两棵树的划分点。但是如果y的父节点也是满的,就必须在插入新的关键字之前将其分裂,最终满节点的分裂会沿着树向上传播。

当沿着树往下查找新的关键字所属位置时,就分裂沿途遇到的每个满节点(包括叶节点本身)。因此,每当要分裂一个满节点y是,就能确保它的父节点不是满的。

分裂B树中的节点

过程B-TREE-SPLIT-CHILD的输入是一个非满的内部节点x和一个使x.ci为x的满子节点的下标i。该过程把这个子节点分裂成两个,并调整x,使之包含更多的孩子。要分裂一个满的根,首先要让根成为一个新的空根节点的孩子,才能使用B-TREE-SPLIT-CHILD.树的高度因此增加1,分裂是树长高的唯一途径。

《B树学习----查询 插入 删除》

《B树学习----查询 插入 删除》

以沿树单程下行方式向B树插入关键字

在一棵高度为h的B树T中,以沿树单程下行方式插入一个关键字k的操作需要O(h)次磁盘存取。所需要的CPU时间为O(th) = O(tlogn)。过程B-TREE-SPLIT-CHILD来保证地柜始终不会降至一个满节点上。

《B树学习----查询 插入 删除》

《B树学习----查询 插入 删除》

辅助的递归过程B-TREE-NONFULL将关键字插入节点x,要求假定在调用该过程时x是非满的。操作B-TREE-INSERT和递归操作B-TREE-INSERT-NONFULL保证了这个假设成立。

《B树学习----查询 插入 删除》

《B树学习----查询 插入 删除》

从B树中删除关键字

B树上的删除操作与插入操作类似,只是略微复杂一下,因为可以从任意一个节点中删除一个关键字,而不仅仅是叶节点,而且当从一个内部节点删除一个关键字是,还要重新安排这个节点的孩子。与插入操作一样,必须防止因删除操作二导致树的结构违反B树性质。就像必须保证一个节点不会因为插入而变得太大一样,必须保证一个节点不会在删除期间变得太小(根节点除外)。

与插入情况相对称,除了根结点外(根结点个数不能少于1),B树的关键字数不能少于t-1个。对于简单删除情况,如果我们定位到关键字处在某个结点中,如果这个结点中关键字个数恰好是t-1个,如果直接删除这个关键字,就会违反B树规则。

此时,需要考虑两种处理方案:

1)把这个结点与其相邻结点合并,合并时需要把父结点的一个关键字加进来,除非相邻的那个结点的关键字数也是t-1个,否则,合并后会超出2t-1的限制,同样违反B树规则。而且,因为从父结点拉下一个关键字,导致父结点的关键字数少1,如果原来父结点关键字数是t-1,那么父结点违反B树规则,这种情况下,必须进行回溯处理。(对于下图(a)初始树,删除结点Z就会出现这种情况)

2)从相邻结点借一个关键字过来,这种情况要求,相邻结点必须有多于t-1个关键字,借的过程中,需要转经父结点,否则违反B树规则。

 

为了避免回溯,要求我们在从树根向下搜索关键字的过程中,凡是遇到途经的结点,如果该结点的关键字数是t-1,则我们需要想办法从其他地方搞个关键字过来,使得该结点的关键字数至少为t。

搞,也是从相邻结点搞,如果相邻结点有的话,当然,也要经过父结点进行周转。如果没有,就说明相邻结点的关键字个数也是t-1,这种情况,直接对该结点与其相邻结点进行合并,以满足要求。

 

B树的结点的合并基于如下情况调用:内结点x的第i个子结点y和第i+1个子结点z的关键字数都是t-1,此时需要把内结点x的第i个关键字下移与y和z的合并,形成一个结点y。

 

B树中结点的合并:

B-TREE-MERGE-CHILD(x, i, y,z)

n[y] ← 2t -1

2 for j ← t +1 to 2t -1

3   do keyj[y] ← keyjt[z]

keyt[y] ← keyi[x]

5 if not leaf[y]

6  then for j ← t +1 to 2t -1

7        do cj[y] ← cjt[z]

8 for j ← i +1 to n[x]

9  do cj[x] ← cj+1[x]

10 n[x] ← n[x] -1

11 FREE-NODE(z)

12 DISK-WRITE(y)

13 DISK-WRITE(z)

14 DISK-WRITE(x)

 

 

B树的删除:

B-TREE-DELETE(T,k)

rroot[T]
if n[r] = 1
 3    then DISK_READ(c1[r])
 4       DISK_READ(c2[r])
 5       y ←c1[r]
 6       z ←c2[r]
 7       if n[y] = n[z] = t-1                   ▹ Cases 2c or 3b
 8         then  B-TREE-MERGE-CHILD(r, 1, y, z) 
 9            root[T] ← y
 10           FREE-NODE(r)
 11           B-TREE-DELETE-NONONE(y, k)
12      else B-TREE-DELETE-NONONE (r, k)
13 else B-TREE-DELETE-NONONE (r, k)
 
考虑到根结点的特殊性,对根结点为1,并且两个子结点都是t-1的情况进行了特殊的处理:
先对两个子结点进行合并,然后把原来的根删除,把树根指向合并后的子结点y。
这样B树的高度就减少了1。这也是B树高度唯一会减少的情况。 

除了这种情况以外,就直接调用子过程B-TREE-DELETE-NONONE (x, k)。

 

B-TREE-DELETE-NONONE (x, k)

i ← 1
if leaf[x]                                       ▹ Cases 1
 3     then while i <= n[x] and k > keyi[x]
 4            do ii + 1
 5               if k = keyi[x]
 6                 then for j ← i+1 to n[x]
 7                        do keyj-1[x] ←keyj[x]
 8                      n[x] ← n[x] - 1
 9                      DISK-WRITE(x)
 10              else error:”the key does not exist”
 11    else while i <= n[x] and k > keyi[x]
12           do ii + 1
 13              DISK-READ(ci[x])
 14              y ←ci[x]
 15              if i <= n[x]
 16                then DISK-READ(ci+1[x])
 17                     z ←ci+1[x]
 18              if k = keyi[x]                          ▹ Cases 2
19                then if n[y] > t-1                   ▹ Cases 2a
 20                       then k′←B-TREE-SEARCH-PREDECESSOR(y)
 21                            B-TREE-DELETE-NONONE (y, k′)
 22                            keyi[x] ←k
 23                     else if n[z] > t-1               ▹ Cases 2b
 24                       then k′←B-TREE-SEARCH-SUCCESSOR (z)
 25                            B-TREE-DELETE-NONONE (z, k′)
 26                            keyi[x] ←k
 27                     else B-TREE-MERGE-CHILD(x, i, y, z)▹ Cases 2c
 28                          B-TREE-DELETE-NONONE (y, k)
 29              else                                   ▹ Cases 3
 30                if i >1
 31                  then DISK-READ(ci-1[x])
 32                       p ←ci-1[x]
 33                if n[y] = t-1 
 34                  then if i>1 and n[p] >t-1               ▹ Cases 3a
 35                         then B-TREE-SHIFT-TO-RIGHT-CHILD(x,i,p,y)
 36                       else if i <= n[x] and n[z] > t-1    ▹ Cases 3a
 37                         then B-TREE-SHIFT-TO-LEFT-CHILD(x,i,y,z)
 38                       else if i>1                       ▹ Cases 3b
 39                         then B-TREE-MERGE-CHILD(x, i, p, y)  
 40                              y ← p
 41                       else B-TREE-MERGE-CHILD(x, i, y, z)▹ Cases 3b
 42                B-TREE-DELETE-NONONE (y, k)
 
查找前驱
B-TREE-SEARCH-PREDECESSOR(y)
1  x ← y
i ← n[x]
3  while not leaf[x]
4    do DISK_READ(ci+1[x])
5       x ←ci+1[x]
6       i ← n[x]
7  return keyi[x]
 
查找后继
B-TREE-SEARCH-SUCCESSOR (z)
1  x ← z
2  while not leaf[x]
3    do DISK_READ(c1[x])
4       x ←c1[x]
5  return key1[x]
 
转移到右边的子结点
B-TREE-SHIFT-TO-RIGHT-CHILD(x,i,y,z)
1 n[z] ← n[z] +1
2 j ← n[z]
3 while j > 1
4   do keyj[z] ←keyj-1[z]
5      j ← j -1
6 key1[z] ←keyi[x]
7 keyi[x] ←keyn[y][y]
8 if not leaf[z]
9   then j ← n[z]
10       while j > 0
11         do cj+1[z] ←cj[z]
12            j ← j -1
13       c1[z] ←cn[y]+1[y]
14 n[y] ← n[y] -1

15 DISK-WRITE(y)

16 DISK-WRITE(z)

17 DISK-WRITE(x)

转移到左边的子结点
B-TREE-SHIFT-TO-LEFT-CHILD(x,i,y,z)
1 n[y] ← n[y] +1
2 keyn[y][y] ← keyi[x]
3 keyi[x] ←key1[z]
4 n[z] ← n[z] -1
5 j ← 1
6 while j <= n[z]
7   do keyj[z] ←keyj+1[z]
8      j ← j +1
9 if not leaf[z]
10  then cn[y]+1[y] ←c1[z]
11       j ← 1
12       while j <= n[z]+1
13         do cj[z] ←cj+1[z]
14            j ← j + 1

15 DISK-WRITE(y)

16 DISK-WRITE(z)

17 DISK-WRITE(x)

注意:每次递归调用前,程序都能保证包括关键字的子树根的关键字数至少为t(除了根结点外),

这是B-TREE-DELETE-NONONE子过程能够正确运行的关键,类似的,

可以用循环不变式证明B-TREE-DELETE-NONONE子过程的正确性。

 

《B树学习----查询 插入 删除》

 

《B树学习----查询 插入 删除》

    原文作者:B树
    原文地址: https://blog.csdn.net/ddhdzt/article/details/49686115
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注