第五部分 高级数据结构
第18章 B树
- B树是为磁盘或其他直播存取的辅助存储设备而设计的一种平衡搜索树,它们在降低磁盘I/O操作数方面要更好一些。
- B树的结点可以有很多孩子
1. B树的定义
一棵B树T是具有以下性质的有根树(根为T.root)
1. 每个结点x有下面属性:
a. x.n,当前存储在结点x中的关键字个数。
b. x.n个关键字本身x.key1, x.key2, …, x.keyx.n,以非降序存放,使用得x.key1 <= x.key2 <= … <= x.keyx.n
c. x.leaf,一个布尔值,如果x是叶结点,则为TRUE;如果x为内部结点,则为FALSE
2. 每个内部结点x还包含x.n + 1个指向其孩子的指针x.c1, x.c2, … , x.cx.n+1。叶结点没有孩子,所以它们的ci属性没有定义
3. 关键字x.keyi对存储在各子树中的关键字范围加以分割:如果ki为任意一个存储在以x.ci为根的子树中的关键字,那么
k1 <= x.key1 <= k2 <= x.key2 <= … <= x.keyx.n <= kx.n+1
4. 每个叶结点具有相同的高度,即树的高度h
5. 每个结点所包含的关键字个数有上界和下界。用一个被称为B树的最小度数的固定整数t >= 2来表示这些界:
a. 除了根结点以外的每个结点必须至少有t - 1个关键字。因此,除了根结点以外的每个内部结点至少有t个孩子。如果树非空,根结点至少有一个关键字
b. 每个结点至多可包含2t -1个关键字。因此,一个内部结点至多可有2t个孩子。当一个结点恰好有2t - 1个关键字时,称该结点是满的。t = 2时的B树是最简单的。每个内部结点有2个,3个或4个孩子,即一棵2-3-4村,然而在实际中,t的值越大,B树的高度就越小。
2. B树上的基本操作
搜索B树
B-Tree-Search(x, k)
i = 1
while i <= x.n and k > x.key(i)
i = i + 1
if i <= x.n and key == x.key(i)
return (x,i)
else if x.leaf
return NIL
else
Disk-Read(x, ci)
return B-Tree-Search(x.ci, k)
创建一棵空的B树
B-Tree-Create(T)
x = allocate-noe()
x.leaf = true
x.n = 0
Disk-Write(x)
T.root = x
向B树中插入一个关键字
分裂B树中的结点
B-Tree-Split-Child(x, i)
z = allocate-node()
y = x.ci
z.leaf = y.leaf
z.n = t - 1
for j = 1 to t - 1
z.key(j) = y.key(j+t)
if not y.leaf
for j = 1 to t
z.cj = y.c(j+t)
y.n = t - 1
for j = x.n + 1 downto i + 1
x.c(j+1) = x.cj
x.c(i+1) = z
for j = x.n downto i
x.key(j+1) = x.key(j)
x.key(i) = y.key(t)
x.n = x.n + 1
Disk-Write(y)
Disk-Write(z)
Disk-Write(x)
以沿树单程下行方式向B树插入关键字
B-Tree-Insert(T, k)
r = T.root
if r.n == 2t - 1
s = allocate-node()
T.root = s
s.leaf = false
s.n = 0
s.ci = r
B-Tree-Split-Child(s, 1)
B-Tree-Insert-Nonfull(s, k)
else
B-Tree-Insert-Nonfull(s, k)
B-Tree-Insert-Nonfull(x, k)
i = x.n
if x.leaf
while i >= 1 and k < x.key(i)
x.key(i+1) = x.key(i)
i = i - 1
x.key(i+1) = k
x.n = x.n + 1
Disk-Write(x)
else
while i >= 1 and k < x.key(i)
i = i - 1
i = i + 1
Disk-Read(x.ci)
if x.ci.n == 2t - 1
B-Tree-Split-Child(x, i)
if k > x.key(i)
i = i + 1
B-Tree-Insert-Nonfull(x.ci, k)
3. 从B树中删除关键字
从B树中删除关键字的短程情况:
1. 关键字k在结点x中,并且x是叶结点,则从x中删除k
2. 关键字k在结点x中,并且x是内部结点,则做以下操作:
a. 结点x中前于k的子结点y至少包含t个关键字,则找出k在以y为根的子树中的前驱k’。递归地删除k’,并在x中用k’代替k
b. 如果y有少于t个关键字,则检查结点x中后于k的子结点z。如果z至少有t个关键字,则找出k在以z为根的子树中的后继k’。递归地删除k’,并在x中用k’代替k
c. 否则,如果y和z都只含有t - 1个关键字,则将k和z的全部合并进y,这样x就丢失了k和指向z的指针,并且y现在包含2t - 1个关键字。然后释放z并递归地从y中删除k
3. 如果关键字k当前不在内部结点x中,则确定必包含k的子树的根x.ci。如果x.ci只有t-1个关键字,必须执行步骤3a或3b来保证降至一个至少包含t个关键字的结点。然后,通过对x的某个合适的子结点进行递归而结束。
a. 如果x.ci只含有t-1个关键字,但是它的一个相邻的兄弟至少包含t个关键字,则将x中的某一个关键字降至x.ci中,将x.ci的相邻左兄弟或右兄弟的一个关键字升至x,将该兄弟中相应的孩子指针移到x.ci中,这样就使得x.ci增加了一个额外的关键字。
b. 如果x.ci以及x.ci的所有相邻兄弟都只包含t-1个关键字,则将x.ci与一个兄弟合并,即将x的一个关键字移至新合并的结点,使之成为该结点的中间关键字。