学习数据结构9-B-树

本文大部分内容基于CSDN的博主@祁峰,B树插入和删除算法的两篇文章。

至于为什么…因为书上写了和没写一样,只能去参考学习……

代码就不放了,因为写的…太烂了…不好放出来误人子弟

(我真的有去动手实现)

 

首先,这棵树不叫B负树。它的出现并不是为了进一步提升查找效率,因为O(logN)已经够快了。它要解决的问题是磁盘较低的I/O速度导致的效率低下。不过,一般只有在数据量大,没法将所有数据一次性读入内存时才会遇到这种问题。所以,就必须降低树的深度,将“瘦高”的树变得“矮胖”。

    ①、树中每个结点至多有m棵子树;
    ②、若根结点不是终端结点,则至少有2棵子树;
    ③、除根之外,所有非终端结点至少有m/2棵子树;
    ④、所有的非终端结点中包含下列信息数据:[n, C0, K0, C1, K1, C2, K2, ...., Kn-1, Cn]
        其中:Ki[i=0,1,...,n-1]为关键字,且Ki<Ki+1[i=0, 1, ..., n-2];Ci[i=0,1,...,n]为指向子树根结点的
        指针,且指针Ci所指子树中所有结点的关键字均小于Ki[i=0,1,...,n-1],但都大于Ki-1[i=1,...,n-1];

这样就引出了多路查找树的概念。 根据AVL树的理论,一颗平衡多路查找树(B~树)自然可以使得数据的查找效率保证在O(logN)这样的对数级别上。而节点内的比较是在内存中进行的,相比于磁盘I/O的速度,耗时几乎可以忽略。所以当树的高度足够低的话,就可以极大的提高效率。

……….

是不是很复杂

………..

根据定义,可以写出节点的结构

typedef struct _btree_node_t
{
    int num;                        /* 关键字个数 */
    int key[max+1];                       /* 关键字:所占空间为(max+1) - 多出来的1个空间用于交换空间使用 */
    struct _btree_node_t child[max+2];   /* 子结点:所占空间为(max+2)- 多出来的1个空间用于交换空间使用 */
    struct _btree_node_t *parent;   /* 父结点 */
}btree_node_t;
/*一开始我还想了一会max+2是什么操作,其实是两个key夹一个child*/

接下来就是这棵树了,树根旁边要存放一些信息

typedef struct
{
    int max;                        /* 单个结点最大关键字个数 树的度(阶数) m=max+1 */
    int min;                        /* 单个结点最小关键字个数 min=m%2? m/2 : m/2+1 */
    int sidx;                       /* 分裂索引 = (max+1)/2 */
    btree_node_t *root;             /* B树根结点地址 */
}btree_t;

树的各种操作中,插入和删除较为复杂。对高度为k的m阶B树,新结点一般是插在叶子层。通过检索可以确定关键码应插入的结点位置。

 B树是从空树起,逐个插入关键字而建立起来的,由于B树结点中的关键字个数num必须>=,因此,每次插入一个关键字不是在
树中添加一个 终端结点,而是首先在最底层的某个非终端结点中插入一个关键字,若该结点的关键字个数不超过m-1,则插入完成,否
则要进行结点的“分裂”。
  假设结点node的关键字个数num>max,则需进行分裂处理,其大体处理流程如下:
  1) 结点node以sidx关键字为分割点,索引(0 ~ sidx-1)关键字继续留在结点node中,索引(sidx+1 ~ num-1)关键字放入新结点node2中
  2) 而索引sidx关键字则插入node->parent中,再将新结点node2作为父结点新插入关键字的右孩子结点
  3) 判断插入node的sidx关键字后,node->parent的关键字个数num是否超过max,如果超过,则以parent为操作对象进行1)的处理;
     否则,处理结束。
以下将通过构造一棵B树的方式来讲解B树的插入过程:假设现在需要构建一棵4阶B树(即:阶m=4、关键字最大个数max=3),其插入操作和处理过程如下描述。
 1) 插入关键字45
    刚开始为空树,因此插入成功后只有一个结点。

 

2) 插入关键字24和53
    在图1的基础上,插入关键字24和53后,该结点关键字个数num仍未超过max,因此不会进行“分裂”处理。
插入完成后,该结点关键字个数num=3已经达到临界值max。

 

3) 插入关键字90

    在图2基础上,插入关键字90后,该结点关键字个数num=4超过max值,需要进行“分裂”处理。
当结点关键字个数num达到max时,则需要进行“分裂”处理,分割序号为num/2。图3中的[4| 24, 45, 53, 90]的
分割序号为num/2 = 4/2 = 2,序号从0开始计数,因此关键字53为分割点,分裂过程如下:

    ->1) 以序列号idx=num/2为分割点,原结点分裂为2个结点A[2| 24, 45]和B[1| 90];
    ->2) 原结点无父结点,则新建一个结点P,并将关键字插入到新结点P中;
    ->3) 将结点A和B作为结点P的子结点,并遵循B树特征④;
    ->4) 因结点P的结点数未超过max,则分裂结束。

4) 插入关键字46和47

    在图3右图的基础上,插入关键字46和47后,得到图4左图,此时结点[4| 24, 45, 46, 47]已经达到分裂条件。
    连续插入关键字46、47后,该结点[2| 24, 45]变为[4| 24, 45, 46, 47],因此其达到了“分裂”的条件,其分裂流程如下:

    ->1) 以序列号idx=num/2为分割点,结点[2| 24, 45, 46, 47]分裂为两个结点A[2| 24, 45]和B[1| 47];
    ->2) 分割点关键字46被插入到父结点P中,得到结点P[2| 46, 53]
    ->3) 新结点B[1| 47]加入到结点P[2| 46, 53]的子结点序列中 - 遵循特征④
    ->4) 因结点P[2| 46, 53]的关键字个数num为超过max,因为分裂结束。
  5) 插入关键字15和18

    在图4右图的基础上,插入关键字15和18后,得到图5左图,此时结点[4| 15, 18, 24, 45]已经达到分裂条件。其处理过程同4)

 6) 插入关键字48、49、50

    在图5右图的基础上插入48、49、50,可得到图6左图,此时结点[1| 47, 48, 49, 50]已达到分裂条件。

完成第一步分裂处理之后,父结点P[4| 24, 46, 49, 53]此时也达到了分裂条件,因而不断分裂。

我自闭了