B-Tree
Index
- B-Tree搜索
- B-Tree插入
- 分裂节点
- 插入节点
- B-Tree删除
- 合并节点
- 删除节点
Basic
B-Tree有两个比较重要的性质:
- 所有的leaf均在同一个level上
- 除了root之外,其它所有node中所储存的数据至少为Minimum Degree - 1,至多为Minimum Degree * 2 - 1
两个结构定义,其中struct TreeNode
定义了B-Tree中的一个node,而struct Tree
则是包含了一个指向B-Tree的root的指针,相当于一个dummy node,设置这个结构的目的是为了可以将其作为参数传入函数中,以便可以在函数中修改root节点的值。
typedef struct TreeNode *PtrBTNode;
typedef struct TreeNode BTNode;
struct TreeNode{
int Num;
Bool IsLeaf;
PtrElementType Key;
PtrBTNode *Child;
};
typedef struct Tree *PtrBT;
struct Tree{
PtrBTNode Root;
};
其他的一些定义和声明,其中ShiftKey
函数用于对每个node中的Key
数组进行部分左移或右移,Direction
控制左移或右移方向(1-右移,0-左移),而Begin
和End
参数则是用来传递被移动部分的最左端和最右端的元素下标,ShiftChild
函数同理针对Child
数组;GetIndex
函数则是用来搜索在一个节点中大于或等于Val
的最小元素的下标,如果均小于Val
,就返回数组的大小(就是刚好越界了):
#define MinDegree 3
typedef int ElementType;
typedef int* PtrElementType;
typedef enum BoolType Bool;
enum BoolType{
False = 0,
True = 1
};
void ShiftKey(PtrElementType Key, Bool Direction, int Begin, int End){
int i;
if(True == Direction){
for(i = End; i >= Begin; i--){
Key[i + 1] = Key[i];
}
}
else{
for(i = Begin; i <= End; i++){
Key[i - 1] = Key[i];
}
}
}
void ShiftChild(PtrBTNode *Child, Bool Direction, int Begin, int End){
int i;
if(True == Direction){
for(i = End; i >= Begin; i--){
Child[i + 1] = Child[i];
}
}
else{
for(i = Begin; i <= End; i++){
Child[i - 1] = Child[i];
}
}
}
int GetIndex(PtrElementType Key, int Size, ElementType Val){
int i;
for(i = 0; i < Size; i++){
if(Key[i] >= Val){
break;
}
}
return i;
}
void BTPrintTree(PtrBTNode Root){
int i;
if(NULL == Root){
return;
}
putchar('[');
for(i = 0; i < Root->Num; i++){
printf("%d", Root->Key[i]);
if(i != Root->Num - 1){
putchar(' ');
}
}
putchar(']');
printf("%d", Root->IsLeaf);
putchar('\n');
for(i = 0; i <= Root->Num; i++){
BTPrintTree(Root->Child[i]);
}
}
void BTCreateTree(PtrBT T){
int i;
//A test case
int a[] = {
12,1,9,2,0,11,7,19,4,15,18,5,14,13,10,16,6,3,8,17,20,21,23};
for(i = 0; i < 23; i++){
BTInsert(T, a[i]);
BTPrintTree(T->Root);
printf("The End\n");
}
}
Search
搜索节点的过程比较简单,下面是函数代码:
PtrBTNode BTSearch(PtrBTNode Root, ElementType Val, int* Index){
int i;
for(i = 0; i < Root->Num&&Val > Root->Key[i]; i++){
;
}
if(i < Root->Num&&Root->Key[i] == Val){
*Index = i;
return Root;
}
else if(True == Root->IsLeaf){
return NULL;
}
else{
return BTSearch(Root->Child[i], Val, Index);
}
}
Insert
在B-Tree中插入一个值,为了不破坏B-Tree的第一个性质,必须将其插入在leaf中,但是又因为B-Tree同时具有第二个性质,所以要保证待插入值一定能成功插入,必须使得所有节点都是非满的,因此在某一个节点成为满节点时,就必须将该节点分裂为两个节点(分裂节点:将满节点分裂为两个包含Minimum Degree - 1个数据的节点,并将原来满节点中数据的中值插入原满节点的父节点中,同时在原满节点的父节点中插入指向两个分裂所得节点的指针,当然实际上只需要插入一个新增指针)。虽然只要leaf保持非满就一定能成功插入,但是当leaf成为满节点分裂时,会有一个数据向上插入到其父节点中,所以必须保证其父节点也非满,如果父节点被插入后成为满节点还需要继续分裂并向上插入数据,依此类推,相当于所有节点都需要保持非满状态。
在插入时向上调整的过程中看似需要递归回溯,其实可以在向下搜索插入位置时,对于即将搜索的下一个节点,都检查其是否为满节点,如果是满节点就将其分裂。而又因为对于搜索路径上的每个节点都进行判断,所以搜索路径上已经搜索过的节点一定是非满的,那么即将搜索到的节点如果需要分裂,分裂后向上插入的那个数据就一定能够插入成功。综上所述,这样的处理方式一定能够成功执行,并且可以保证数据一定能够插入成功。
分裂节点
这个函数传入的是指向待分裂节点的父节点的指针和指向待分裂节点的指针在其父节点Child数组中的下标。
首先用BTAllocateNode
函数新建一个节点NewNode
,将待分裂节点的Minimum Degree - 1个数据和Minimum Degree个Child指针转移到NewNode
中,同时应当注意NewNode
和待分裂节点应该处于同一层,即两者Isleaf
成员的值应当保持一致(即下面代码段中的Caution注释行),再将待分裂节点的父节点中Key
数组和Child
数组的部分元素向右移动,为之后的插入腾出位置,最后将待分裂节点中数据的中值插入其父节点中,并将指向NewNode
的指针也插入待分裂节点的父节点中。
void BTChildSplit(PtrBTNode SplitNodeP, int ChildIndex){
int i;
PtrBTNode NewNode = BTAllocateNode();
PtrBTNode FullNode = SplitNodeP->Child[ChildIndex]