看了一点B树的基础知识,记录下来。
在大规模数据存储中,实现索引查询这样一个实际背景下,树结点存储的元素数量是有限的(如果元素数量非常多的话,查找就退化成结点内部的线性查找)这样导致二叉查找树结构由于树的深度过大而造成磁盘I/O的频繁读写,进而查询效率低下,那么如何减小输的深度呢?(当然是不能减小查询的数据量),一个基本思想:采用多叉结构。
刚开始一直以为B-树 与B树是不同的,这些名字翻译的实在让人歧义。其实B树 = B-树;
B-树的定义:B-树是一种平衡的多路查找树,一棵m阶的B-树,或为空树,或为满足下列特性的m叉树;
(1)树中每个节点至多有m棵子树;
(2)若根结点不是叶子结点,则至少有两棵子树;
(3)除根结点外,所有非终端的节点至少由[ m/2 ]向下上取整;
(4)所有的非终端中包含下列信息数据:
(n,A0,K1,A1,.....,Kn,An)
(5)所有叶子结点出现在同一层上,并且不带信息。
将B-树的数据结构写一下“
typedef struct BTNode{
int keynum; //结点中关键字的个数,即结点大小
struct BTNode *parent; // 指向双亲指针
KeyType key[m+1] ; //关键字向量,0号单元未用
struct BTNode *ptr[m+1] ;// 子树指针向量
Record *recptr[m+1] ;// 记录指针向量,0号单元未用,应该是关键字对应的物理地址
}BTNode ,*BTree;
typedef struct {
BTNode *pt; //指向找到的结点;
int i; // 1....m 在结点中关键字的序号
int tag ; // 1 查找成功,0 查找失败
}Result;
由于B-树通常存储在磁盘上,故查找总是现在磁盘上找到指针p所指向的结点后,先将结点信息读入内存,然后利用顺序查找或者折半查找查询等于K的关键字。 显然,磁盘查找要比内存查找慢得多,因此关键字所在结点在B-树上的层次数,是决定B-树查找效率的首要因素。
B-树的插入:
首先在树中查找K,若找到则直接返回,否则查找操作必失败与某个叶子结点上,然后将K插入该叶子结点中。若该叶子结点非满(即节点中原有的关键字总数小于m-1),则插入关键字K后并未破坏B-树的性质;若该结点原为满,则插入后,违反B-树性质,将违反性质的结点一中间位置上的关键字key[m/2](向上取整) 为划分点,分裂为两个结点。关键字key[ m/2 ]插入到双亲结点,双亲结点的丰满情况,类似分析。
B-树的删除:
若被删关键字K所在的结点非树叶,则用K的后继结点K'取代K,然后从叶子中删除K',删除后的调整分为三种情况”
(1)x->keynum >Min(关键字的个数大于性质需要的最小个数),则只需删除K指针即可。
(2)若x->keynum = Min ,该叶子中的关键字个数已经是最小值,删K及其右指针会破坏B-树的性质。若*x的左或者右邻兄弟结点*y 中的关键字数目大于min,则将*y中的最大或最小关键字上移至双亲*parent中,而将*parent中相应的关键字下移至x中。显然这种移动时的双亲中关键字数目不变;*y被移除一个关键字,故keynum-1。移动完成,删除结束;
(3)若*x及其相邻的左右兄弟中的关键字数目均为最小值Min,则上述的移动操作就不奏效,此时必须*x和左或右邻兄弟合并。不仿设*x有右邻兄弟*y,在*x中删去K及其右子树之后,将双亲结点*parent中介于*x,*y 之间的关键字K,作为中间关键字,与*x,*y中的关键字一起合并为一个新的结点取代*x,*y。因为*x和*y原来各有Min个关键字,从双亲中一如的K'抵消了从*x中删除的K,故新结点中恰有2Min个关键字,没有破坏B-树的结构;但由于K'从双亲中移到新结点后,相当于*parent中删除了K',若parent->keynum原大于Min,则删除操作到此结束;否则,同样要通过移动*parent的左右兄弟中过的关键字,或将*parent与其左右兄弟合并的方法维护B-树性质。最坏的情况,合并操作会向上传播至根,当跟中只有一个关键字时,合并操作会使根结点及其两个孩子合并成一个新的根,从而使整棵树的高度减少一层。
再来说一说B+树
B+树是应文件系统所需要而出的一种B-树的变形树,一棵m阶的B+树和m阶的B-树差异在于:
(1)有n棵子树的结点中含有n个关键字;
(2)所有叶子结点中包含了全部关键字信息,及指向关键字记录的指针;
(3)所有非终端结点可以看成索引,结点中仅含有其子树中的最大或最小的关键字;
B树只适合随机搜索,B+树适合随机搜索和顺序搜索。实际应用中比较多。
为啥说B+树比B-树更适合实际应用中的文件索引和数据库索引呢?
(1)B+树中,内部结点并没有存储指向关键字具体信息的指针,因此nebulizer其内容结点相对于B-树更小,一次性读入内存的需要查找的关键字也越多,相对I/O读写次数降低。
(2)B+树查询效率更稳定。B+树中非终端结点中关键字的索引并不是最终指向文件内容的结点,而只是叶子结点的索引,所以任何关键字的查找必须走一条根结点到叶子结点的路,所存关键字查询路径相同,导致每个数据查询效率相当。
B*树是B+树的变体,在B+树的非根和非叶子结点中再增加指向兄弟的指针,B*树定义了非叶子结点关键字至少为(2/3)*M ,即块的最低使用率为三分之二,代替B+树的1/2;
B+树的分裂只影响原结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针。当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针。
B*树分裂会影响兄弟结点。当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点,再在原结点插入关键字,最后修改父结点中的关键字,如果兄弟结点也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针。
所以B*树分配新结点的概率比B+树要低,空间使用率更高。
总结:
B树 = 有序数组+平衡多叉树
B+树 = 有序数组链表 + 平衡多叉树
B*树 = 丰满的B+ 树
红色部分是我在其他博客里看到的,我还不太理解,哪位大神给我讲解下?
为啥是有序数组 和有序数组链表???