《算法导论》-- B树

2018-04-30  本文已影响21人  10xjzheng

1 概述

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

如图,当B树的一个内部结点x包含x.n个关键字,那么结点x就有x.n+1个孩子。结点x中的关键字就是分隔点,它把节点x中所处理的关键字的属性分隔为x.n+1个子域。当一课B树中查找一个关键字时,基于对存储在x中的x.n个关键字的比较,做出一个(x.n+1)路的选择。叶结点的结构和内部结点的结构不同,后面讨论。


image.png

2 辅存上的数据结构

磁盘结构:


image.png

磁盘有两个机械运动的部分:盘片旋转和磁臂移动,这是一次I/O耗费时间的原因所在。磁盘每次存取多个数据项。由于大多数系统中,一个B树算法的运行时间主要由它所执行的DISK-READ和DISK-WRITE操作的次数决定,所以我们希望这些操作能够读或写尽可能的信息。因此,一个B树结点通常和一个完整磁盘叶一样大,并且一个磁盘页的大小限制了一个B树结点可以含有的孩子个数。

3 B树的定义

任何和关键字相关联的数据将于关键字一样存放在同一个结点,实际上可能只是存放一个指针,这个指针存放该关键字对应的数据的磁盘页。B+树把所有的数据存在叶子结点,内部结点只存放关键字和孩子指针,因此最大化了内部节点的分支因子。

一棵B树是具有以下性质的有跟树:

4 B树的高度

h <= log(t) (n+1)/2 (t为对数的底)

5 B树的基本操作

5.1 搜索

伪代码如下

B-TREE-SEARCH(x, k)  //x为节点,k为插入关键字
i = 1  //
while i <= x.n and k > x.key(i) //搜索k所在的节点位置
    i = i + 1
 if i <= x.n and k == x.key(i)
    return (x, i)
elseif x.leaf  //当前是子节点,返回NIL
    return NIL
else  DISK-READ(x, c(i)) //查找子节点
    return B-TREE-SEARCH(x.c(i), k) 
5.2 插入
创建一棵空的B树

为构造一棵B树T,先用B-TREE-CREATE来创建一个空的根结点,然后调用B-TREE-INSERT来添加新的关键字。这些过程都要用一个辅助过程ALLOCATE-NODE,它在O(1)时间内为一个新节点分配一个磁盘页,我们可以假定由ALLOCATE-NODE所创建的结点并不需要DISK-READ,因为磁盘上还没有关于该结点的有用信息。

B-TREE-CREATE(T)
x = ALLOCATE-NODE()
x.leaf = TRUE
x.n = 0
DISK-WRITE(x)
T.root = x
分裂

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

与一个二叉搜索树一样,可以在从树根到叶子这个单程向下过程中将一个新的关键字插入B树中,为了做到这一点,我们并不是等到找出插入过程中实际要分裂的满结点时才做分裂。相反,当沿着树往下查找新的关键字所属位置时,就分裂沿途遇到的每个满结点(包括叶结点本身)。因此,每当要分裂一个满结点y时,就能确保它的父结点不是满的。

分裂B树中的结点
image.png
B-TREE-SPLIT-CHILD(x, i) //x是结点,i为x的满子节点的下标
z = ALLOCATE-NODE()
y = x.c(i)
z.leaf = y.leaf
z.n = t - 1
for j = 1 to t - 1  // 复制关键字
    z.key(j) = y.key(j + 1)
if not y.leaf // 复制孩子指针
    for j = 1 to t
        z.c(j) y.c(j+t)
y.n = t - 1    
for j = x.n + 1 downto i + 1 //上层增加一个子节点,关键字和孩子结点要向前推进1
    x.c(j + 1) = x.c(j)
x.c(i + 1) = z
for j = x.n downto i 
    x.key(j + 1) = x.key(j)
x.key(i) = y.key(i)
x.n = x.n + 1
DISK-WRITE(y)
DISK-WRITE(z)
DISK-WRITE(x)
沿树单程下行方式向B树插入关键字

在一颗高度为h的B树T中,沿树单程下行方式插入一个关键字k的操作需要O(h)次磁盘存取。所需要的CPU时间为O(th) = O(tlog(t)n)。过程B-TREE-INSERT利用B-TREE-SPLIT-CHILD来保证递归始终不会降至一个满结点上。

//把关键字k插入T树中
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.c1= 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, c(i))
        if x.c(i).n ==  2t - 1
            B-TREE-SPLIT-CHILD(x, i) 
            if k > x.key(i)
                i = i + 1
       B-TREE-INSERT-NONFULL(x.c(i), k)
image.png
5.3 删除

必须防止删除操作而导致树的结构违反B树的性质。下面简要介绍删除操作如何工作:

上一篇下一篇

猜你喜欢

热点阅读