磁盘作为辅存,它的容量要比内存大得多,但是速度也要慢许多,下面就是磁盘的的结构图:
磁盘驱动器由一个或多个盘片组成,它们以固定的速度绕着主轴旋转,数据存储于盘片的表面,磁盘驱动器通过磁臂末尾的磁头来读写盘片。礠臂可以将磁头向主轴移近或移远。当一个磁头处于静止的时候,它下面经过的磁盘表面称为磁道。
磁盘之所以比主存要慢,是因为它有机械运动的部分:盘片旋转和磁臂运动。为了摊还机械移动所花费的等待时间,磁盘会一次存取多个数据项。磁盘上的数据被组织成页面。每次磁盘读写的数据都是以页面为单位。
本章考虑运行时间主要考虑:磁盘存取次数以及CPU时间。尽管磁盘读写时间依赖于当前磁道和所需磁道之间的距离以及磁盘的初始旋转状态,但是仍然使用读写的页数作为磁盘存取时间的近似值。
在一个典型的B树应用中,需要处理的数据非常大,以至于所有数据无法一次装入主存。通过下面的代码对磁盘操作进行建模。设x为指向一个对象的指针,如果该对象在主存中,那么可以像平时一样引用该对象的属性。如果x所指的对象不在主存中,那么在引用该对象的属性之前,需要先执行DISK-READ(x),将该对象读入主存中。(如果对象已经在主存中,则DISK-READ(x)是个空操作)。同样,如果x所指的对象的属性改变了,则需要通过DISK-WRITE(x)写入磁盘:
x = a pointer to some object
DISK-READ(x)
operations that access and/or modify the attributes of x
DISK-WRITE(x) // omitted if no attributes of x were changed
other operations that access but do not modify attributes of x
一个B树算法的运行时间主要由DISK-READ和DISK-WRITE操作的次数决定,希望这些操作能够读写尽可能多的信息。因此,一个B树节点通常和一个完整磁盘页一样大。B树就是为磁盘设计的一种平衡搜索树。许多数据库系统使用B树或者B树的变种来存储信息。
B树类似于红黑树,不同之处在于B树可以有很多孩子,也就是B树的分支因子可以很大,所以对于n个节点,B树的严格高度要比红黑树小得多。下图就是一个简单的B树:
如果B树的内部节点x包含x.n个关键字,则节点x就有x.n+1个孩子。节点x中的关键字就是分隔点,他把所有的关键字分隔为x.n+1个子域,每个子域都由x的一个孩子处理。
一:B树的定义
一颗B树具有下面的性质:
1:每个节点x有下面属性:
4:所有叶节点具有相同的深度,树的高度为h。
5:每个节点包含的关键字个数有上界和下界。用固定整数t>=2(t称为B树的最小度数)表示:
a:除了根节点之外,每个节点至少包含t-1个关键字,所以除了根节点之外,每个内部节点至少有t个孩子。
b:每个节点至多包含2t-1个关键字,因此,每个内部节点最多有2t个孩子,如果一个节点有2t-1个关键字时,称该节点是满的。
比如,如果t=2,则每个内部节点有2,3,或者4个孩子。B树上的大多数操作所需的磁盘存取次数与树的高度成正比,对于B树的高度,有下面的定理:
如果n>=1,那么包含n个关键字,高度为h,最小度数为t>=2的B树满足:h<=。
证明如下:B树的根至少包含一个关键字,其他节点至少包含t-1个关键字,所以B树在深度为1,至少包含2个节点,在深度2,至少包含2t个节点,在深度3,至少包含2
个节点。所以,在深度h,至少包含2个节点。所以有:n>=
1+(t-1) = 2
-1。所以有:h<= 。注意;n表示关键字的个数,而非节点个数。
二:B树上的基本操作
讨论B树的基本操作如B-TREE-SEARCH, B-TREE-CREATE和B-TREE-INSERT之前,做如下约定:
a:B树的根结点始终在主存中,因而无需对根做DISK-READ;但是,每当根结点被改变后,都需要对根结点做一次DISK-WRITE。
b:任何被当作参数的结点被传递之前,要先对它们做一次DISK-READ。
1:搜索
B-TREE-SEARCH操作,输入时一个指向某子树根节点x的指针,以及要搜索的关键字k。因此,顶层调用的形式为B-TREE-SEARCH(T.root, k)。如果k在B树中,那么B-TREE-SEARCH返回的是由节点y和使得y.
== k的下标组成的序对(y, i);否则返回NULL,代码如下:
B-TREE-SEARCH过程,访问的磁盘页面数为O(h) = O(),总的CPU时间为O(th)
= O()
2:创建一个空的B树
为构造一颗B树T,先用B-TREE-CREATE来创建一个空的根节点,然后调用B-TEE-INSERT来添加关键字:
B-TREE-CREATE需要O(1)次的磁盘操作和O(1)的CPU时间。
3:插入关键字
B树中插入关键字要比二叉搜索树中的插入复杂。首先要查找插入新关键字的叶节点的位置,然而不同于二叉搜索树的插入,在B树中,不能简单的创建一个新的叶节点,因为会破坏B树的性质,所以要将关键字插入一个已经存在的叶节点上。
由于不能将新的关键字插入一个满的叶节点,所以需要将满的节点y(有2t-1个关键字)按照中间关键字y.分裂为两个各含t-1个关键字的节点。中间关键字被提升到y的父节点。但是如果y的父节点也是满的,则必须在插入新的关键字之前就将其分裂。
为了插入新的关键字,可以从根到叶节点的向下过程中将一个关键字插入,当沿着树向下查找新的关键字所属位置时,就分裂沿途遇到的每个满节点。因此,每当要分裂一个满节点时,可以确保它的父节点不是满的。
B-TREE-SPLIT-CHILD的输入是一个非满的内部节点x,以及下标i,这个下标表示的x的子节点x.
为满子节点。要分裂一个根,则首先创建一个新的节点,然后使根成为该节点的孩子,此时在调用B-TREE-SPLIT-CHILD。此时树的高度加1,分裂时树长高的唯一途径。B-TREE-SPLIT-CHILD代码如下:
B-TREE-SPLIT-CHILD占用的CPU时间为Ө(t),执行O(1)次磁盘操作。下图展示了该过程:
在一裸高度为h的B树T中,插入一个关键字k的操作是在沿树下降的过程中一次完成的。共需要O(h)次磁盘存取,所需的CPU时间为O(th)
= O()。B-TREE-INSERT代码如下:
其中的B-TREE-INSERT-NONFULL代码如下:
三:从B树中删除关键字
B树上的删除操作与插人操作类似,只是稍微有点复杂,因为一个关键字能够从任意一个结点中删除,而不只是可以从叶子中删除:从一个内部结点中删除关键字时,需要重新安排这个结点的子女;必须防止因删除操作而导致树的结构违反B树性质,必须保证一个结点不会在删除期间变得太小(只有根结点除外)。
过程B-TREE-DELETE从以x为根的子树中删除关键字k。这个过程必须保证了无论在何时,x的关键字个数都至少等于最小度数t。注意这个条件要求比通常的B树中最少的关键字数多1个的关键字。这是因为该过程在递归下降时,需要把x的关键字移动到子节点中。
如果,同时也保证了树根必须包含至少一个关键字(除非树是空的)的性质。
下面,我们来大致描述一下删除操作的工作过程:
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。
上述两种情况本质上相同,针对2a,如下图:
将结点x中的k替换为 后,因为y的关键字个数为t,所以可以递归的调用B-TREE-DELETE(y, k')即可。
c:否则,如果y和z都只有t-1个关键字,则将k和z中所有关键字合并进y,使得x失去k和指向z的指针,这使y包含2t-1个关键字。然后,释放z并递归的将k从y中删除。
如下图:
结点y和z合并,成为新的y结点,同时将x中关键字k并入新的y结点作为中间节点,此时y包含2t-1个关键字,具有2t个子节点,所以可以递归的调用B-TREE-DELETE(y,k)即可。
3:如果关键字k不在内结点x中。则确定包含k的的子树的根x.
(如果k确实在树中的话)。如果x.有t个关键字,则递归的调用B-TREE-DELETE(x.,
k)即可。
如果x. 只有t-1个关键字,执行步骤3a或3b以保证我们降至一个包含至少t个关键字的结点,然后,通过对x的某个合适的子结点进行递归而结束:
a:如果x.
只包含t-1个关键字,但它的一个相邻兄弟包含至少t个关键字.则将x中的某一个关键字降至x.中,将x.
的相邻左兄弟或右兄弟中的某一关键字升至x,将该兄弟中合适的子女指针移到x. 中,这样使得x.
增加一个额外的关键字。
如下图:
经过上面的转换之后,x. 有了t个关键字,则递归的调用B-TREE-DELETE(x.,
k)即可。
b:如果x.
以及x.
的所有相邻兄弟都包含t-1个关键字,则将x.
与一个兄弟合并,即将x的一个关键字移至新合并的结点,使之成为该结点的中间关键字。
如下图:
经过上面的转换之后,x. 包含2t-1个关键字,所以可以递归的调用B-TREE-DELETE(x.,k)即可。
因为一棵B树中的大部分关键字都在叶结点中,我们可以预期在实际中,删除操作主要是用于从叶结点中删除关键字。这样B-TREE-DELETE过程只要沿树下降一趟即可,而无需任何回溯。但是,当删除某内结点中的一个关键字时,该程序也沿树下降一趟,但可能还要返回删除了关键字的那个结点,以用其前驱或后继来取代被删除的关键字(情况2a和情况2b}。
这个过程虽然看似复杂,但对一棵高度为h的B树,它只需O(h)次磁盘存取操作,因为在递归调用该过程之间,仅需O(1)次对DISK-READ和DISK-WRITE的调用。所需CPU时间为O(th)
= O( )。