B-树

B-树的定义

一颗m阶B树,或为空树,或为满足下列特性的m叉树:

  1. 树中每个结点最多含有m棵子树;
  2. 若根结点不是叶子结点,则至少有两颗子树;
  3. 除根之外的所有非终端结点至少有 ⌈m/2⌉ 棵子树 ;
  4. 所有的叶子结点都出现在同一层次上,并且不带信息,通常称为失败结点。失败结点不存在,指向这些结点的指针为空。引入失败结点是为了便于分析B-树的查找性能。
  5. 结点的结构如下:
    (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
  6. 从上述定义可以看出,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;
}                                     

运行结果:

在这里插入图片描述
返回顶部

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值