Introduce to algorithm--------pseudo code to C/C++ code (chapter 18)

B树

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

B树与红黑树的不同之处B树的结点可以有很多孩子,从数个到数千个。

B树以一种自然的方式推广了二叉搜索树。

Introduce to algorithm--------pseudo code to C/C++ code (chapter 18)_第1张图片

二叉搜索树的每个结点构成一个数据范围,每个节点的左孩子小于等于该节点本身,右孩子大于等于该节点本身。从父节点到子节点构成一个数据的范围。于此相似,B树只是分支多,不再固定在两个,可以有成千上百个。如上图,每个节点可以包含多个数据,每个数据的左孩子都是小于该数据本身,右孩子都是大于该数据本身。

在一个典型的B树应用中,所要处理的数据量非常大,以至于所有数据无法一次装入主存。B树算法将所需页面从磁盘复制到主存,然后将修改过的页面写回磁盘。在任何时刻,B树算法都只需在主存中保持一定数量的页面。因此,主存大小并不限制被处理的B树大小。

由此可见,B树是典型的应用在I/O方面的数据结构,以优化I/O为主要目的。可以想象一个几G的资源,不可能把内存全部用来加载这个资源,那样不现实。较为通用的就是只加载部分资源,等到需要读取其它资源时再从磁盘加载并替换已加载资源,若有加载资源被修改则需要写回磁盘。由此也引出了下面两个重要的磁盘操作:
Introduce to algorithm--------pseudo code to C/C++ code (chapter 18)_第2张图片

任何时刻,这个系统可以在主存中保持有限的页数。假定系统不再将被使用的页从主存中换出。

页面的换出与页面调度有关,比较复杂,这里不介绍。

由于在大多数系统中,一个B树算法的运行时间主要由它所执行的DISK_READ和DISK_WRITE操作的次数决定,所以我们希望这些操作能够读或写尽可能多的信息。因此,一个B树节点通常和一个完整磁盘页一样大,并且磁盘页的大小限制了一个B树节点可以含有的孩子个数。

这里简介机械硬盘。机械硬盘主要构件为盘片、磁臂。一般是多个盘片绕同一个主轴高速运转,磁盘又分多个磁道,实际上就是同心圆环。为读写方便以及提高I/O效率,每个磁道又分为多个扇区,就是多个扇形区域,每次读取或写入都是以块为单位(-_-好像是),每个磁道的扇区个数并不确定,有多种情况。磁臂尾端有磁头,在磁头处发生磁盘的读写。
可以想见,机械硬盘的读写时间主要消耗在:

  • 1.寻道(寻找磁道)—-磁头要移动到磁道位置。
  • 2.旋转延迟(-_-名词记不清了)—-磁头需要等待盘片旋转到要读写的扇区。

因为是机械移动,相比内存读写,磁盘读写的访问时间要慢好几个数量级,好像磁盘访问是毫秒级的,内存访问是纳秒级的。所以B树算法的运行时间主要由磁盘读写时间决定。

B树的定义

一颗B树T是具有以下性质的有根树(根为T.root):

1.每个节点x有以下属性:

a.x.n,当前存储在结点x中的关键字个数。

b.x.n个关键字本身x.key1,x.key2,…,以非降序存放,使得x.key1 <= x.key2 <=…。

c.x.leaf,一个布尔值,如果x是叶节点,则为TRUE;如果x为内部节点,则为FALSE。

2.每个内部结点x还包含x.n + 1个指向其孩子的指针x.c1,x.c2,…。叶节点没有孩子,所以他们的cn属性没有定义。

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

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

5.每个节点所包含的关键字个数有上界和下界,用一个被称为B树的最小度数(minimum degree)的固定整数t >= 2来表示这些界:

a.除了根结点以外的每个节点必须至少有t-1个关键字。因为,除了根结点以外的每个内部结点至少有t个孩子。如果树非空,根结点至少有一个关键字。

b.每个节点至多可包含2t - 1个关键字。因此,一个内部结点至多可有2t个孩子。当一个结点恰好有2t - 1个关键字时,称该结点是满的(full)。

上面定义摘自算法导论,可能最小度数t(minimum degree)的定义有些不清晰, 这是构造B树时人为定义的(-_-据我观察)。

B树上的基本操作

在这些过程中,我们采用两个约定:

  • B树的根结点始终在主存中,这样无需对根做DISK_READ操作;然而,当根结点被改变后,需要对根结点做一次DISK_WRITE操作。
  • 任何被当作参数的结点在被传递前,都要对他们做一次DISK_READ操作。

搜索

Introduce to algorithm--------pseudo code to C/C++ code (chapter 18)_第3张图片

创建一颗空的B树

Introduce to algorithm--------pseudo code to C/C++ code (chapter 18)_第4张图片

上述两个操作与二叉搜索树相似,不再描述。需要注意,B树中的结点多以为单位。

分裂B树中的结点

过程B-TREE-SPLIT-CHILD的输入是一个非满的内部结点x(假定在主存中)和一个使x.ci(也假定在主存中)为x的满子节点的下标i.该过程把这个子节点分裂成两个,并调整x,使之包含多出来的孩子。

如下图:
Introduce to algorithm--------pseudo code to C/C++ code (chapter 18)_第5张图片

Introduce to algorithm--------pseudo code to C/C++ code (chapter 18)_第6张图片

对伪代码不再描述,分裂图很好地展示了代码的意图。需要注意的是伪代码结尾对三个有过改动的结点(也即页)进行了写回。

以沿树单程下行方式向B树插入关键字

在一颗高度为h的B树中,以沿树单程下行方式插入一个关键字k的操作需要O(h)次磁盘存取。

Introduce to algorithm--------pseudo code to C/C++ code (chapter 18)_第7张图片

如果根结点为满,则分裂根结点以致其非满(为了满足B树的性质:每个节点最多可包含 2t-1 个关键字)。接着在B-TREE-INSERT-NONFULL中进行实质上的插入工作。具体插入不再详述,与二叉搜索树的插入相似。

从B树种删除关键字

在B树种删除关键字时,与二叉搜索树不同。在B树中删除时,是在一个包含多个关键字的节点中删除,并不是删除结点。但其实也没有什么难,只是操作上的繁复而已。与红黑树相似,在B树种删除也受到树性质的影响。主要是结点中关键字数量的保证,在最大数量和最小数量之间。

删除关键字时,也是因为要保证结点中关键字的数量,工作会复杂一点。

  • 1.如果关键字k所在的结点是叶节点,删除之。
  • 2.如果关键字所在的结点是内部节点,
  • a.如果前于关键字k的子节点或后于k的子节点至少有t个关键字,则找出关键字k的前驱或后驱,删除之,并用前驱或后驱代替k。
  • b.否则将关键字k与它的的前结点和后结点合并,并删除k。
  • 3.如果关键字k不再当前节点,则先确定包含k的子树的根,若子树根结点只有t-1个关键字,执行以下步骤;否则,重复步骤1、2:
  • a.如果相邻结点含有多于t-1个关键字,则将父节点的某个关键字下降至当前节点,并将相邻节点的某个关键字上升至父节点。
  • b.相邻节点都只包含t-1个关键字,则与一个相邻节点合并(导论中将父节点的一个关键字也合并在其中,不是很懂)。

感觉步骤三的执行是为了后续执行的顺利,可以在以后执行删除时有更好的效率。
理解还是图来的好:

Introduce to algorithm--------pseudo code to C/C++ code (chapter 18)_第8张图片

详见算法导论

你可能感兴趣的:(Introduce,to,algorithm)