参考算法导论第三版算法
1.B树的定义spa
任何和关键字相联系的“卫星数据”将于关键字同样存放在同一个节点中。指针
一棵B树T是具备如下性质的有根树(根为T.root):递归
1.每一个节点x都有下面属性:ci
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属性没有定义。im
3.关键字x.keyi对存储在各子树种的关键范围加以分割:若是ki为任意一个存储在以x.ci为根的子树中的关键字,那么error
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树种插入一个关键字
将一个满的节点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树插入关键字
在一棵高度为h的B树T中,以沿树单程下行方式插入一个关键字k的操做须要O(h)次磁盘存取。所须要的CPU时间为O(th) = O(tlogn)。过程B-TREE-SPLIT-CHILD来保证地柜始终不会降至一个满节点上。
辅助的递归过程B-TREE-NONFULL将关键字插入节点x,要求假定在调用该过程时x是非满的。操做B-TREE-INSERT和递归操做B-TREE-INSERT-NONFULL保证了这个假设成立。
从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)
1 n[y] ← 2t -1
2 for j ← t +1 to 2t -1
3 do keyj[y] ← keyj-t[z]
4 keyt[y] ← keyi[x]
5 if not leaf[y]
6 then for j ← t +1 to 2t -1
7 do cj[y] ← cj-t[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)
1 r ← root[T]
2 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)
1 i ← 1
2 if leaf[x] ▹ Cases 1
3 then while i <= n[x] and k > keyi[x]
4 do i ← i + 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 i ← i + 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
2 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子过程的正确性。