摘要:B树的基本定义:每个节点x都有以下域:
a)n[x],当前存储在节点x中的关键字数
b)n[x]个关键字本身,以非降序存放;
c)leaf[x],是一个布尔值,标志是否是叶子.
(2)每个节点内还包含n[x]+1个指向其子女的指针;
(3)每个节点都具有相同的深度h。
(4)每个节点能包含的关键字数有一个上界和下界,这些界可以用一个称作B数的最小度数的固定整数t来表示.t>=2;
(5)每个非根节点必须至少有t-1个关键字,每个非根节点内至少有t个子女。
(6)每个节点至多包含2t-1个关键字,所以一个内节点至多有2t个子女;
基本操作:
(1)搜索B树,搜索B树与搜索二叉查找树很相似,只是在每个节点所做的不是个二叉搜索,而是由该节点的子女数决定的.基本思路是遍历该节点的所有关键字,直到要搜索的元素x小于等于某个关键字或者大于所有关键字.
然后判断要搜索的元素就等于临界关键字,或者小于它.或者大于所有关键字.如果该节点是叶子,则没有子女,否则根据情况判断是否继续向下搜索.(这一部分的代码将直接运用到删除例程中)
(2)插入数据:因为B树的特点,因此必须将新的数据插入到一个叶子节点中.但是又不能将关键字插入到满叶子节点,故引入一个分裂操作,将一个满的节点从中间分裂成两个各含t-1个关键字的节点,而中间关键字则被提升到父节点.如果父节点也是满的,则它必须在关键字被插入前分裂.因此在查找过程中,遇到满节点就分裂,这样可以保证每分裂一个满节点时它的父节点不是满的。
int maxIndex(Position p){
int i=0;
for(;i<2*DU;i++){
if(p->data[i]==0)
return i-1; //如果遇到了0说明前一个下标是最大的下标
}
return 2*DU-2; //如果到了这里说明该节点是满节点,最大关键字下标是2*DU-2
}
void shiftLeft(Position p,int start,int offset)
{ //start是开始移动的位置,offset 移动的位移
int tmp=start;
int max=maxIndex(p);
if(max==0)
max=1;
for(;tmp<=2*DU-2;tmp++)
{ //左移关键字
p->data[tmp]=p->data[tmp+offset];
p->data[tmp+offset]=0; //将无用的关键字置为0
}
tmp=start;
for(;tmp<=2*DU-1;tmp++)
{
p->child[tmp]=p->child[tmp+offset]; //左移子节点指针
p->child[tmp+offset]=NULL; //将无用的子节点指针置为NULL
}
}
void shiftRight(Position p,int start)
{
//向右移动一位
int max=maxIndex(p);
int index=max; //index 是key的下标
for(;index>=start;index--)
p->data[index+1]=p->data[index]; //右移关键字数组
for(index = max + 1;index>=start;index--)
p->child[index+1] = p->child[index];
}
Position splitPage(Position p,int val)
{
//p节点为需要分裂的满节点,val是用来决定返回递归的新节点是B节点(原来的节点)还是新分裂的C节点
int mid=p->data[DU-1]; //取得p中间位置的节点,它被分裂到父节点中
Position right = pm(); //新建C节点
Position result;
int index=DU;
int i=0;
for(;index<=2*DU-2;index++)
{
//开始复制B节点的关键字数组和子节点指针数组,
right->data[i]=p->data[index];
p->data[index]=0;
right->child[i]=p->child[index];
p->child[index]=NULL;
if(right->child[i]!=NULL)
right->child[i]->parent=right;
//B节点的一部分子节点迁移到了C节点上,所以要重新设置子节点的父节点指针
i++;
}
right->child[i]=p->child[2*DU-1];//子节点指针数组比关键字数组个数多一个
if(right->child[DU-1]!=NULL)
right->child[DU-1]->parent=right; //同时设置最后一个子节点指针的父节点
p->child[2*DU-1]=NULL;
p->data[DU-1]=0;
right->isLeaf=p->isLeaf; //设置C节点是否是叶子节点,
if(p->parent==NULL)
{
//如果p节点就是根节点了,那么需要新生成Root节点
Position root = pm();
p->parent=root;
root->isLeaf=0;
}
Position parent=p->parent;
int first=0;
for(;first<= 2*DU-2;first++){//取得父节点中mid可插入的位置
if(parent->data[first]>mid||parent->data[first]==0)
break;
}
index=2*DU-3;
for(;index>=first;index--)
{
//关键字和子节点指针数组右移1位
parent->data[index+1]=parent->data[index];
parent->child[index+2]=parent->child[index+1];
}
parent->data[first]=mid; //插入新的关键字mid,和新的子节点指针right
parent->child[first+1]=right;
parent->child[first] = p;//避免生成新节点第一个子节点信息缺少
right->parent=parent;
result = val > mid ? right:p; //确定返回的节点
return result;
}
void InsertData(Position p,int val)
{
//p表示目的节点,val表示要插入的值
if(pageSize(p)==2*DU-1){ //如果递归的过程中碰到满节点,就先分裂p节点
p=splitPage(p,val); //分裂的过程后,重新设置p节点
}
int index=0;
for(;index<sizeof(p->data)/sizeof(int);index++)
{ //寻找子节点的位置,因为分裂节点保证了p节点不是满节点,所以不用考虑满节点的情况(满节点的话index需要加1)
if(p->data[index]==0||p->data[index]>val){
break;
}
}
if(p->isLeaf==0)
{
//如果不是叶子节点,递归插入到子节点
InsertData(p->child[index],val);
return ;
}
if(pageSize(p)>0) //如果节点中有关键字则右移1个位置
shiftRight(p,index);
p->data[index]=val; //把新的关键字插入到相应的位置
return;
}