B-树的定义
一颗m阶B树,或为空树,或为满足下列特性的m叉树:
- 树中每个结点最多含有m棵子树;
- 若根结点不是叶子结点,则至少有两颗子树;
- 除根之外的所有非终端结点至少有 ⌈m/2⌉ 棵子树 ;
- 所有的叶子结点都出现在同一层次上,并且不带信息,通常称为失败结点。失败结点不存在,指向这些结点的指针为空。引入失败结点是为了便于分析B-树的查找性能。
- 结点的结构如下:
(n,P0,K1,P1,K2,P2,…,Kn,Pn)
其中
①Ki(1≤i≤n)为关键字,且关键字按升序排序。
②指针Pi(0≤i≤n)指向子树的根结点。Pi-1相当于指向Ki的“左子树”,Pi相当 于指向Ki的“右子树”。
③关键字的个数n必须满足:⌈m/2⌉-1≤n≤m-1 - 从上述定义可以看出,B-树具有平衡、有序、多路的特点。
B-树的存储结构
typedef struct node{ //B树存储结构
int keynum; //结点关键字个数
KeyType key[m+1]; //关键字数组,key[0]未使用
struct node *parent; //双亲结点指针
struct node *ptr[m+1]; //孩子结点指针数组
}BTNode,*BTree;
B-树的查找
在B-树上的查找,是一个顺指针查找结点(与二叉树的查找一样),然后在结点查找关键字的过程。
-
查找结果类型定义:
typedef struct{ BTNode *pt; //指向找到的结点 int i; //在结点中的关键字位置; int tag; //查找成功与否标志 }Result;
-
查找
/* 在树t上查找关键字k,返回结果(pt,i,tag)。 若查找成功,则特征值tag=1,关键字k是指针pt所指结点中第i个关键字 否则特征值tag=0,关键字k的插入在pt所指结点的第i个和第i+1个关键字之间 */ Result SearchBTree(BTree t,KeyType k){ BTNode *p=t,*q=NULL; //初始化结点p和结点q,p指向待查结点,q指向p的双亲 int found=FALSE; //设定查找成功与否标志 int i=0; Result r; //设定返回的查找结果 while(p&&!found){ i=SearchBTNode(p,k); //在结点p中查找关键字k,使得p->key[i]<=k<p->key[i+1] if(i>0&&p->key[i]==k) //找到待查关键字 found=OK; //查找成功 else{ //查找失败 q=p; p=p->ptr[i]; } } if(found){ //查找成功 r.pt=p; r.i=i; r.tag=1; } else{ //查找失败 r.pt=q; r.i=i; r.tag=0; } return r; //返回关键字k的位置(或插入位置) }
B-树的插入
插入26时,d结点引起分裂。
插入85时,g结点引起分裂,继而引起e结点分裂。
最后插入7时,先引起c结点分裂,继而引起b结点分裂,又引起a结点分裂。树的深度加一。
/*
在树t上结点q的key[i]与key[i+1]之间插入关键字k。
若引起结点过大,则沿双亲链进行必要的结点分裂调整
使t仍是B树
*/
void InsertBTree(BTree &t,int i,KeyType k,BTree p){
BTNode *q;
int finished,newroot_tag,s; //设定需要新结点标志和插入完成标志
KeyType x;
if(p==NULL) //t是空树
NewRoot(t,k,NULL,NULL); //生成仅含关键字k的根结点t
else{
x=k;
q=NULL;
finished=FALSE;
newroot_tag=FALSE;
while(!finished&&!newroot_tag){
InsertBTNode(p,i,x,q); //将关键字x和结点q分别插入到p->key[i+1]和p->ptr[i+1]
if (p->keynum<=Max)
finished=OK; //插入完成
else{
s=(m+1)/2;
SplitBTNode(p,q); //分裂结点
x=p->key[s];
if(p->parent){ //查找x的插入位置
p=p->parent;
i=SearchBTNode(p, x);
}
else //没找到x,需要新结点
newroot_tag=OK;
}
}
if(newroot_tag==OK) //根结点已分裂为结点p和q
NewRoot(t,x,p,q); //生成新根结点t,p和q为子树指针
}
}
B-树的删除
B-树的删除有点复杂,主要就是涉及到删除元素后,要调整树使得还是B-树。关键就是使得:关键字的个数n必须满足:⌈m/2⌉-1≤n≤m-1
第一种,删除12,只需删除关键字和相应指针,树的其它部分不变。
第二种,删除上图中的50,因为它的有兄弟结点中的关键字个数大于 ⌈m/2⌉-1,可以拿走一个,需要做如下调整:
第三种,删除上图中的53,因为它的有兄弟结点中的关键字个数不大于 ⌈m/2⌉-1,不能再拿了,只能合并了,需要做如下调整:
int BTNodeDelete(BTree p,KeyType k){
//在结点p中查找并删除关键字k
int i;
int found; //查找标志
if(p==NULL)
return 0;
else{
found=FindBTNode(p,k,i); //返回查找结果
if(found){ //查找成功
if(p->ptr[i-1]!=NULL){ //删除的是非叶子结点
Substitution(p,i); //寻找相邻关键字(右子树中最小的关键字)
BTNodeDelete(p->ptr[i],p->key[i]); //执行删除操作
}
else
Remove(p,i); //从结点p中位置i处删除关键字
}
else
found=BTNodeDelete(p->ptr[i],k); //沿孩子结点递归查找并删除关键字k
if(p->ptr[i]!=NULL)
if(p->ptr[i]->keynum<Min) //删除后关键字个数小于MIN
AdjustBTree(p,i); //调整B树
return found;
}
}
总的代码:
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define FALSE 0
#define MAXM 10 //定义B树的最大的阶数
const int m=3; //设定B树的阶数
const int Max=m-1; //结点的最大关键字数量
const int Min=(m-1)/2; //结点的最小关键字数量
typedef int KeyType; //KeyType为关键字类型
typedef struct node{ //B树存储结构
int keynum; //结点关键字个数
KeyType key[m+1]; //关键字数组,key[0]未使用
struct node *parent; //双亲结点指针
struct node *ptr[m+1]; //孩子结点指针数组
}BTNode,*BTree;
typedef struct{
BTNode *pt; //指向找到的结点
int i; //在结点中的关键字位置;
int tag; //查找成功与否标志
}Result;
int InitBTree(BTree &t){ //初始化B树
t=NULL;
return OK;
}
int SearchBTNode(BTree p,KeyType k){ //在结点p中查找关键字k的插入位置i
int i=0;
for(i=0;i<p->keynum&&p->key[i+1]<=k;i++);
return i;
}
/*
在树t上查找关键字k,返回结果(pt,i,tag)。
若查找成功,则特征值tag=1,关键字k是指针pt所指结点中第i个关键字
否则特征值tag=0,关键字k的插入位置为pt结点的第i个
*/
Result SearchBTree(BTree t,KeyType k){
BTree p=t,q=NULL; //初始化结点p和结点q,p指向待查结点,q指向p的双亲
int found=FALSE; //设定查找成功与否标志
int i=0;
Result r; //设定返回的查找结果
while(p&&!found){
i=SearchBTNode(p,k); //在结点p中查找关键字k,使得p->key[i]<=k<p->key[i+1]
if(i>0&&p->key[i]==k) //找到待查关键字
found=OK; //查找成功
else{ //查找失败
q=p;
p=p->ptr[i];
}
}
if(found){ //查找成功
r.pt=p;
r.i=i;
r.tag=1;
}
else{ //查找失败
r.pt=q;
r.i=i;
r.tag=0;
}
return r; //返回关键字k的位置(或插入位置)
}
void InsertBTNode(BTree &p,int i,KeyType k,BTree q){
//将关键字k和结点q分别插入到p->key[i+1]和p->ptr[i+1]中
int j;
for(j=p->keynum;j>i;j--){ //整体后移空出一个位置
p->key[j+1]=p->key[j];
p->ptr[j+1]=p->ptr[j];
}
p->key[i+1]=k;
p->ptr[i+1]=q;
if(q!=NULL)
q->parent=p;
p->keynum++;
}
void SplitBTNode(BTree &p,BTree &q){
//将结点p分裂成两个结点,前一半保留,后一半移入结点q
int i;
int s=(m+1)/2;
q=(BTNode *)malloc(sizeof(BTNode)); //给结点q分配空间
q->ptr[0]=p->ptr[s]; //后一半移入结点q
for(i=s+1;i<=m;i++){
q->key[i-s]=p->key[i];
q->ptr[i-s]=p->ptr[i];
}
q->keynum=p->keynum-s;
q->parent=p->parent;
for(i=0;i<=p->keynum-s;i++) //修改双亲指针
if(q->ptr[i]!=NULL)
q->ptr[i]->parent=q;
p->keynum=s-1; //结点p的前一半保留,修改结点p的keynum
}
void NewRoot(BTree &t,KeyType k,BTree p,BTree q){
//生成新的根结点t,原p和q为子树指针
t=(BTNode *)malloc(sizeof(BTNode)); //分配空间
t->keynum=1;
t->ptr[0]=p;
t->ptr[1]=q;
t->key[1]=k;
if(p!=NULL) //调整结点p和结点q的双亲指针
p->parent=t;
if(q!=NULL)
q->parent=t;
t->parent=NULL;
}
/*
在树t上结点q的key[i]与key[i+1]之间插入关键字k。
若引起结点过大,则沿双亲链进行必要的结点分裂调整
使t仍是B树
*/
void InsertBTree(BTree &t,int i,KeyType k,BTree p){
BTNode *q;
int finished,newroot_tag,s; //设定需要新结点标志和插入完成标志
KeyType x;
if(p==NULL) //t是空树
NewRoot(t,k,NULL,NULL); //生成仅含关键字k的根结点t
else{
x=k;
q=NULL;
finished=FALSE;
newroot_tag=FALSE;
while(!finished&&!newroot_tag){
InsertBTNode(p,i,x,q); //将关键字x和结点q分别插入到p->key[i+1]和p->ptr[i+1]
if (p->keynum<=Max)
finished=OK; //插入完成
else{
s=(m+1)/2;
SplitBTNode(p,q); //分裂结点
x=p->key[s];
if(p->parent){ //查找x的插入位置
p=p->parent;
i=SearchBTNode(p, x);
}
else //没找到x,需要新结点
newroot_tag=OK;
}
}
if(newroot_tag==OK) //根结点已分裂为结点p和q
NewRoot(t,x,p,q); //生成新根结点t,p和q为子树指针
}
}
void Remove(BTree p,int i){ //从p结点删除key[i]和它的孩子指针ptr[i]
int j;
for(j=i+1;j<=p->keynum;j++){ //前移删除key[i]和ptr[i]
p->key[j-1]=p->key[j];
p->ptr[j-1]=p->ptr[j];
}
p->keynum--;
}
void Substitution(BTree p,int i){
//查找被删关键字p->key[i](在非叶子结点中)的替代叶子结点(右子树中值最小的关键字)
BTNode *q;
for(q=p->ptr[i];q->ptr[0]!=NULL;q=q->ptr[0]);
p->key[i]=q->key[1]; //复制关键字值
}
void MoveRight(BTree p,int i){
/*将双亲结点p中的最后一个关键字移入右结点q中
将左结点aq中的最后一个关键字移入双亲结点p中*/
int j;
BTNode *q=p->ptr[i];
BTNode *aq=p->ptr[i-1];
for(j=q->keynum;j>0;j--){ //将右兄弟q中所有关键字向后移动一位
q->key[j+1]=q->key[j];
q->ptr[j+1]=q->ptr[j];
}
q->ptr[1]=q->ptr[0]; //从双亲结点p移动关键字到右兄弟q中
q->key[1]=p->key[i];
q->keynum++;
p->key[i]=aq->key[aq->keynum]; //将左兄弟aq中最后一个关键字移动到双亲结点p中
p->ptr[i]->ptr[0]=aq->ptr[aq->keynum];
aq->keynum--;
}
void MoveLeft(BTree p,int i){
/*将双亲结点p中的第一个关键字移入左结点aq中,
将右结点q中的第一个关键字移入双亲结点p中*/
int j;
BTNode *aq=p->ptr[i-1];
BTNode *q=p->ptr[i];
aq->keynum++; //把双亲结点p中的关键字移动到左兄弟aq中
aq->key[aq->keynum]=p->key[i];
aq->ptr[aq->keynum]=p->ptr[i]->ptr[0];
p->key[i]=q->key[1]; //把右兄弟q中的关键字移动到双亲节点p中
q->ptr[0]=q->ptr[1];
q->keynum--;
for(j=1;j<=aq->keynum;j++){ //将右兄弟q中所有关键字向前移动一位
aq->key[j]=aq->key[j+1];
aq->ptr[j]=aq->ptr[j+1];
}
}
void Combine(BTree p,int i){
/*将双亲结点p、右结点q合并入左结点aq,
并调整双亲结点p中的剩余关键字的位置*/
int j;
BTNode *q=p->ptr[i];
BTNode *aq=p->ptr[i-1];
aq->keynum++; //将双亲结点的关键字p->key[i]插入到左结点aq
aq->key[aq->keynum]=p->key[i];
aq->ptr[aq->keynum]=q->ptr[0];
for(j=1;j<=q->keynum;j++){ //将右结点q中的所有关键字插入到左结点aq
aq->keynum++;
aq->key[aq->keynum]=q->key[j];
aq->ptr[aq->keynum]=q->ptr[j];
}
for(j=i;j<p->keynum;j++){ //将双亲结点p中的p->key[i]后的所有关键字向前移动一位
p->key[j]=p->key[j+1];
p->ptr[j]=p->ptr[j+1];
}
p->keynum--; //修改双亲结点p的keynum值
free(q); //释放空右结点q的空间
}
void AdjustBTree(BTree p,int i){
//删除结点p中的第i个关键字后,调整B树
if(i==0) //删除的是最左边关键字
if(p->ptr[1]->keynum>Min) //右结点可以借
MoveLeft(p,1);
else //右兄弟不够借
Combine(p,1);
else if(i==p->keynum) //删除的是最右边关键字
if(p->ptr[i-1]->keynum>Min) //左结点可以借
MoveRight(p,i);
else //左结点不够借
Combine(p,i);
else if(p->ptr[i-1]->keynum>Min) //删除关键字在中部且左结点够借
MoveRight(p,i);
else if(p->ptr[i+1]->keynum>Min) //删除关键字在中部且右结点够借
MoveLeft(p,i+1);
else //删除关键字在中部且左右结点都不够借
Combine(p,i);
}
int FindBTNode(BTree p,KeyType k,int &i){
//反映是否在结点p中是否查找到关键字k
if(k<p->key[1]){ //结点p中查找关键字k失败
i=0;
return 0;
}
else{ //在p结点中查找
i=p->keynum;
while(k<p->key[i]&&i>1)
i--;
if(k==p->key[i]) //结点p中查找关键字k成功
return 1;
}
}
int BTNodeDelete(BTree p,KeyType k){
//在结点p中查找并删除关键字k
int i;
int found_tag; //查找标志
if(p==NULL)
return 0;
else{
found_tag=FindBTNode(p,k,i); //返回查找结果
if(found_tag==1){ //查找成功
if(p->ptr[i-1]!=NULL){ //删除的是非叶子结点
Substitution(p,i); //寻找相邻关键字(右子树中最小的关键字)
BTNodeDelete(p->ptr[i],p->key[i]); //执行删除操作
}
else
Remove(p,i); //从结点p中位置i处删除关键字
}
else
found_tag=BTNodeDelete(p->ptr[i],k); //沿孩子结点递归查找并删除关键字k
if(p->ptr[i]!=NULL)
if(p->ptr[i]->keynum<Min) //删除后关键字个数小于MIN
AdjustBTree(p,i); //调整B树
return found_tag;
}
}
void BTreeDelete(BTree &t,KeyType k){
//构建删除框架,执行删除操作
BTNode *p;
int a=BTNodeDelete(t,k); //删除关键字k
if(a==0) //查找失败
printf(" 关键字%d不在B树中\n",k);
else if(t->keynum==0){ //调整
p=t;
t=t->ptr[0];
free(p);
}
}
void DestroyBTree(BTree &t){
//递归释放B树
int i;
BTNode* p=t;
if(p!=NULL){ //B树不为空
for(i=0;i<=p->keynum;i++){ //递归释放每一个结点
DestroyBTree(*&p->ptr[i]);
}
free(p);
}
t=NULL;
}
int print(BTree p)
{
if(p==NULL)
return 0;
BTree q;
printf("[");
for(int i = 1;i <= p->keynum;i++)
{
printf("%d ",p->key[i]);
}
printf("]");
for(int i = 0;i < p->keynum+1;i++)
{
if(p->ptr[i]!=NULL)
{
q = p->ptr[i];
print(q);
}
}
}
void Test(){
BTree T=NULL;
Result s; //设定查找结果
KeyType a[]={45,24,70,7,30,53,90,3,12,26,37,50,61,85,100};
printf("创建一棵%d阶B树:\n",m);
for(int j=0;j<15;j++){ //逐一插入元素
s=SearchBTree(T,a[j]);
if(s.tag==0)
InsertBTree(T,s.i,a[j],s.pt);
printf(" \n第%d步,插入元素%d:\n ",j+1,a[j]);
print(T);
}
printf("\n");
printf("删除操作:\n");
KeyType k; //删除操作
k=7;
BTreeDelete(T,k);
printf(" 删除%d:\n ",k);
printf(" 删除后的B树: \n");
print(T);
printf("\n");
k=100;
BTreeDelete(T,k);
printf(" 删除%d:\n ",k);
printf(" 删除后的B树: \n");
print(T);
printf("\n");
printf(" 递归释放B树\n"); //递归释放B树
DestroyBTree(T);
print(T);
}
int main(){
Test();
return 0;
}