数据结构——B树

前言:

B树一棵平衡多路查找树,在文件系统和磁盘读取中很实用,效率比较高。

定义:

B树T是具有以下性质的有根树(引用自《算法导论》):

 1)  每个节点x有下面的属性:

      a)    《数据结构——B树》是x中的关键字个数,若x是B树中的内节点,则x《数据结构——B树》+1个子女。

      b)    《数据结构——B树》个关键字本身,以非降序排列,《数据结构——B树》

      c)    《数据结构——B树》,布尔值,如果x是叶节点,则为TRUE,若为内部节点,则为FALSE

 2)  每个内节点x包含《数据结构——B树》+1个指向其子女的指针《数据结构——B树》

 3)  如果《数据结构——B树》为存储在以《数据结构——B树》为根的子树中的关键字,则《数据结构——B树》

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

 5)  B树的最小度数t

      a)    每个非根的节点必须至少有t-1个关键字,除根节点外的每个内部节点至少有t个孩子。

      b)    每个节点可包含至多2t-1个关键字。

B树的结构:

《数据结构——B树》

B树的高度:

如果《数据结构——B树》,那么对任意一棵包含n个关键字、高度为h、最小度数为《数据结构——B树》的B树T,有:

《数据结构——B树》

B树的基本操作:

B树和红黑树一样,可以进行搜索、插入和删除等操作。

搜索B树:

    搜索一棵B树和搜索一棵二叉查找树类似,只是在每个节点所做的分支选择是根据节点的孩子数做出多路分支选择。更严格的说,对每个内部节点x,做的是《数据结构——B树》+1路的分支选择。

    B_TREE_SEARCH(x,k)表示输入是一个指向某子树根节点x的指针,以及要在该子树中搜索的一个关键字k。根节点x出发,对每个节点,找到等于k关键字的Key[i]则查找成功;否则在c[i]中递归搜索k,直到到达叶子节点,如仍未找到则说明关键字不在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 k == x.key(i)
        return (x,i);
    else if x.leaf
            return NULL;
        else DISK-READ(x,c(i));
            return B_TREE_SEARCH(x.c(i),k);

下面举个例子:在所给的B树中查找关键字为k=31所在的节点和相应的下标。

《数据结构——B树》

说明:上图例子中是查找关键字k=31的过程,红色小方框表示节点查找的路径,黄色表示指向孩子指针的路径。

  1. k=31与根节点的关键字比较,在这里根节点只有一个关键字key=25,因为k>key,则沿着C2指针的方向查找。
  2. C2指向的节点包含两个关键字key[1]=29和key[2]=35,因为key[1]<k<key[2],则继续沿着C2指针的方向查找。
  3. C2指向的节点包含两个关键字key[1]=31和key[2]=34,因为k=key[1],则查找成功,返回k所在的节点和小标i=1;

插入关键字:

    插入节点的过程是:首先创建一棵空的B树,然后再逐一插入关键字。在插入关键字的过程中要考虑节点为满的情况,当节点处于满时,再插入关键字就需要对该节点进行分裂操作,使该树能够满足B树的性质。

创建一棵空的B树:

一棵空的B树是指该树只有一个空的根节点,没有其他任何信息。用B_TREE_CREATE(T)来创建一个空的根节点,使该树T为空的B树。

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

向B树插入关键字:

    B树中插入关键字要比二叉查找树插入关键字复杂,因为插入关键字后,必须使其满足B树的性质。若将关键字插入到一个满的节点y(有2t-1个关键字),则必须将该节点y的中间关键字y.key[t]分裂为两个各含t-1个关键字的节点,中间关键字升级为节点y的父节点,以标志两棵新树的划分点。若y的父节点也是满的节点,则必须在插入关键字之前将其分裂,最终满节点的分裂会沿着树向上传播。下面介绍B树的分裂节点。

分裂B树中的节点:

    分裂过程B_TREE_SPLIT_CHILD的输入是一个非满的内部节点x和一个使x.c[i]为x的满子节点的小标i。该过程把子节点分裂成为两个节点,并调整x,使其包含多出来的孩子。要分裂一个满根,首先要先让根成为一个新的空根节点的孩子,这样才能使用B_TREE_SPLIT_CHILD。树的高度因此增加1,分裂是树长高的唯一途径。

B_TREE_SPLIT_CHILD(x,i)
	z = ALLOCATE-NODE()
	y = x.c[i]		//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
		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[t]
	x.n = x.n+1
	DISK_WRITE(y)
	DISK_WRITE(z)
	DISK_WRITE(x)

下面给出分裂节点的例子:分裂一个t=4的节点。
《数据结构——B树》

    说明:节点《数据结构——B树》分为两个节点y和z,y的中间关键字S被提升到y的父节点中。
    介绍完分裂节点后,我们就可以对B树进行插入关键字,将关键字插入到一个最小度数为t的B树时有以下情况:

  1. 若关键字所插入的节点有足够的空间,即该节点的关键字个数小于2t-1;则把关键字按非降序顺序插入到该结点。
  2. 若关键字所插入的节点没有足够的空间,即该结点的关键字个数为2t-1;则需把该结点进行分裂。

B-TREE-INSERT(T,k)

	r = T.root
	if r.n == 2t-1
		  then s = ALLOCATE-NODE()
			  T.root = s       
			  s.n = 0
			  s.leaf = FALSE
			  s.c[1] = 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
	then while i >= 1 and k < x.key[i]
		     do 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]
			 do i = i-1
		 i = i+1
		 DISK-READ(x.c[i])       
		 if x.c[i].n == 2t-1
			then B-TREE-SPLIT-CHILD(x,i)
				 if k > x.key[i]
					then i = i+1
		 B-TREE-INSERT-NONFULL(x.c[i],k)

下面举例子:B树的最小度数t=3,下面是插入节点的过程:
《数据结构——B树》

    说明:灰色(阴影)节点表示插入过程中所经历过的节点,蓝色关键字表示插入的关键字,黄色关键字表示升级为父节点关键字,绿色表示指向孩子的指针。具体步骤如下:
(a)初始B树。
(b)向初始B树插入关键字B的结果;这是对叶子节点的简单插入,叶子节点的关键字个数小于2t-1=5,不用分裂。
(c)将关键字Q插入前一棵B树中的结果;节点RSTUV被分裂为两个包含RS和UV的节点,关键字T被提升到根节点中,关键字Q被插入到两半的最左边(RS节点)。
(d)将关键字L插入到前一棵B树中的结果;由于根节点是满的,因此立即被分裂,同时B树的高度增加1.然后将关键字L被插入到包含JK的叶节点中。
(e)将关键字F插入到前一棵B树中的结果;在将关键字F插入两半的最右边(DE节点)之前,节点ABCDE会进行分裂。关键字C提升到父节点。

删除关键字:

    从B树中删除关键字时,要考虑B树的性质,必须保证删除关键字之后节点的关键字个数不小于t-1。若节点的关键字个数小于t-1,则必须通过合并来满足B树的性质。删除关键字的过程必须在B树查找该关键字,确保正确删除该关键字,删除的过程中分两个部分:第一部分是该关键字在叶子节点中,叶子节点的关键字个数可能只有t-1个,也可能大于t-1个;第二种部分是该关键字在内部节点,则判断该关键字是否有左右孩子节点,即孩子指针所指向的节点,并且对左右孩子节点的关键字个数情况进行分析。

从B树中删除关键字会出现以下3种情况:

   1、如果关键字k在叶子节点x中,且叶子节点x的关键字个数大于t-1,则直接删除该关键字k。

   2、如果关键字k在内部节点x中,则根据以下的情况进行分析:

       a、小于节点x关键字k的子节点y,如果节点y至少包含t个关键字,则找出k在以节点y为根的子树中的前驱关键字previous,递归地删除previous,并且在节点x中用关键字previous替换关键字k的值。

       b、如果节点y少于t个关键字,若节点z为小于关键字k的x节点的子节点,且z至少有t个关键字,则找出k在以节点z为根的子树中的后继关键字Successor,递归地删除Successor,并且在节点x中用关键字Successor替换关键字k的值。

       c、若节点z和y都只含有t-1个关键字,则将关键字k和节点z的所有关键字一起合并到节点y中,这样节点x就失去关键字k和指向子节点z的指针,并且节点y包含有2t-1个关键字。然后释放节点z的内存并且递归地删除节点y中的关键字k。

   3、如果关键字k不在内部节点x中,则确定必包含k的子树的根《数据结构——B树》。若《数据结构——B树》只有t-1个关键字,则执行a或b操作。然后,对合适的子节点递归删除关键字k。

       a、若《数据结构——B树》只包含t-1个关键字,但它的相邻兄弟包含至少t个关键字,则将x中的某一个关键字降至《数据结构——B树》,将《数据结构——B树》的相邻兄弟中的某一个关键字升至x,将该兄弟中合适的孩子指针迁移到《数据结构——B树》中。

       b、若《数据结构——B树》与其所有相邻兄弟节点都包含t-1个关键字,则将《数据结构——B树》与一个兄弟合并,即将x的一个关键字移至新合并的节点,使其成为该节点的中间关键字。

下面举例子:B树的最小度数t=3;

《数据结构——B树》

《数据结构——B树》

    说明:灰度(阴影)关键字节点表示删除过程中经历过的节点;黄色表示关键字上升为父节点的关键字;红色表示关键字下降为子节点的关键字;绿色表示指向子节点的指针。

(a)原始B树。

(b)删除关键字F;这是情况1:从叶子节点简单删除该关键字。

(c)删除关键字M;这是情况2a:关键字M的前驱关键字L提升并占据关键字M的位置。

(d)删除关键字G;这是情况2c:关键字G下降以构成节点DEGJK,然后从这个叶节点删除G。

(e)删除关键字D;这是情况3b:将根节点关键字P下降,并且合并节点CL和节点TX;再删除关键字D。

(f)删除关键字B;这是情况3a:移动关键字C以填补关键字B的位置,移动关键字E填补关键字C的位置。

    原文作者:B树
    原文地址: https://blog.csdn.net/chenhanzhun/article/details/38643335
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞