B树

    B树是为磁盘或其他直接存取的辅助设备而设计的一种平衡搜索树,B树类似于红黑树,但它们在降低磁盘I/O操作数方面更好一些,许多数据库系统使用B树或者B树的变种来存储信息。

B树的定义

    为简单起见,任何和关键字相联系的“卫星数据”将与关键字一样存放在同一个结点中。
    一棵B树T是具有以下性质的有根树(根为T.root):

  1. 每个结点x有下面的属性:
        a. x.n,当前存储在结点x中的关键字个数。
        b. x.n个关键字本身x.key1,x.key2,•••,x.key(x.n),以非降序存放,使得x.key1≤x.key2≤•••≤x.key(x.n)。
        c. x.leaf,一个布尔值,如果x是叶结点,则为TRUE;如果x是内部结点,则为FALSE。

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

  3. 关键字x.keyi对存储在各子树中的关键字范围加以分割:如果ki为任意一个存储在以x.ci为根的子树中的关键字,那么
        k1≤x.key1≤k2≤x.key2≤•••≤x.key(x.n)≤k(x.n+1)。

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

  5. 每个结点所包含的关键字个数有上界和下界。用一个被称为B树的最小度数的固定整数t≥2来表示这些界。
        a. 除了根结点以外的每个结点必须至少有t-1个关键字。因此,除了根结点以外的每个内部结点至少有t个孩子。如果树非空,根结点至少有一个关键字。
        b. 每个结点至少可包含2t-1个关键字。因此,一个 内部结点至多可有2t个孩子。当一个结点恰好有2t-1个关键字时,称该结点是满的。

B树的基本操作

    在进行各个操作时给出如下的规定:
- DISK_READ(x)的功能是将对象读入主存,DISK_WRITE(x)用来保存对象x的属性所做的任何修改。它们先做一次DISK_READ操作 。
- B树的根节点始终在主存中,这样无需对根做DISK_READ操作;然而,当根节点被改变后,需要对根结点做一次DISK_WRITE操作。
- 任何被当做参数的结点在被传递之前,都要对它们先做一次DISK_READ操作。

搜索

    B_TREE_SEARCH是定义在二叉搜索树上的TREE_SEARCH过程的一个直接推广。它的输入时一个指向某子树根结点x的指针,以及要在该子树中搜索的一个关键字k。因此,顶层调用的形式为B_TREE_SEARCH(T, root, k)。如果k在B树中,那么B_TREE_SEARCH返回的是由结点y和使得y.keyi=k的下标i组成的有序对(y,i);否则,过程返回NIL。

B_TREE_SEARCH(x, k)
i = 1
while i <= x.n and k > x.keyi
  i = i +1
if i <= x.n and k == x.keyi
  return (x, i)
else if x.leaf
  return NIL
else DISK_READ(x, ci)
  return B_TREE_SEARCH(x.ci, k)

创建空树

    为构造一棵B树T,先用B_TREE_CREATE来创建一个空的根结点,然后调用B_TREE_INSERT来添加新的关键字。这些过程都要用到一个辅助过程ALLOCATE_NODE,它为一个新结点分配一个磁盘页。

B_TREE_CREATE(T)
x = ALLOCATE_NODE()
x.leaf = TRUE
x.n = 0
DISK_WRITE(x)
T.root = x

分裂

    过程B_TREE_SPLIT_CHILD 的输入是一个非满的内部结点x(假定在主存中)和一个使x.ci(假定也在主存中)为x的满子结点的下标i。该过程把这个子结点分裂成两个,并调整x,使之包含多出来的孩子。要分裂一个满的根,首先要让根成为一个新的空根结点的孩子,这样才能使用B_TREE_SPLIT_CHILD。树的高度因此增加1;分裂是树长高的唯一途径。

B_TREE_SPLIT_CHILD(x, i)
z = ALLOCATE_NODE()
y = z.ci
x.leaf = y.leaf
z.n = t – 1
for j = 1 to t – 1
  z.keyi = 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
  z.key(j+1) = x.keyi
x.keyi = y.keyt
x.n = x.n + 1
DISK_WRITE(y)
DISK_WRITE(z)
DISK_WRITE(x)

插入

    将新的关键字插入一个已经存在的叶结点上,由于不能将关键字插入一个满的叶结点,所以要通过分裂操作将结点划分,中间关键字被提升到父结点,以标示两颗新树的划分点,如果父结点也是满的,就会在插入新的关键字之前将其分裂,最终满结点的分裂会沿着树向上传播。B_TREE_INSERT利用B_TREE_SPLIT-CHILD来保证递归始终不会降至一个满结点上。同时过程通过调用B_TREE_INSERT_NONFULL完成将关键字k插入以非满的根结点为根的树中。

B_TREE_INSERT(T, k)
r = T.root
if r.n == 2 * t – 1
  s = ALLOCATE_NODE()
  T.root = s
  s.leaf = FALSE
  s.n = 0
  s.c1 = r
  B_TREE_SPLIT_CHILD(s, 1)
  B_TREE_INSERT_NONFULL(s, k)
else 
  B_TREE_INSERT_NONFULL(r, k)
B_TREE_INSERT_NONFULL(x, k)
i = x.n
if x.leaf
  while i >= 1 and k < x.keyi
    x.key(i+1) = x.keyi
    i = i1
  x.key(i+1) = k
  x.n = x.n + 1
  DISK_WRITE(x)
else 
  while i >= 1 and k < x.keyi
    i = i1
  i = i + 1
  DISK_READ(x.ci)
  if x.ci.n == 2 * t – 1
    B_TREE_SPLIT_CHILD(x, i)
    if k > x.keyi
      i = i + 1
  B_TREE_INSERT_NONFULL(x.ci, k)

删除

    过程B_TREE_DELETE以从x为根的子树中删除关键字k,这个过程必须保证无论何时,结点x递归调用自身时,x中关键字个数至少为最小度数t。删除关键字的各种情况如下:

  1. 如果关键字k在结点x中,并且x是叶结点,则从x中删除k。

  2. 如果关键字k在结点中,并且x是内部结点,则进行一下操作:
        a. 如果结点x中前于k的子结点y至少包含t个关键字,则找出k在以y为根的子树中的前驱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的一个关键字移至新合并的结点,使之成为该结点的中间关键字。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值