本文大部分内容基于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]此时也达到了分裂条件,因而不断分裂。