数据结构实战(十一)——B-树应用

任务简述

设计并实现m(m小于10)阶的B-树的一种应用。
[基本要求]
(1) 随机生成数据
(2) 以图形方式显示该B-树
(3) 实现m阶B-树的插入、删除、查找功能。

算法描述

typedef了两个类型,第一个是b-树的结点,第二个是每次查找的结果。B-树的第一个关键字key【0】空着不用,孩子结点ptr全用。先依次插入n个数,再删除前两个插入的数。插入前先检查树中是否有该数,没有则在叶子结点插入该数。插入时判断是否有根结点,没有则生成根节点,有则进行插入并判断是否完成插入(结点关键字数符合要求),没有则需要分裂。自下而上逐层判断至插入完成或到根节点,若需要新的根节点,则新建一根节点。删除时逐层递归查找该数据,找到后进行删除操作。若不是叶子结点,则找到其右边最小的叶子结点代替它并删除该叶子结点,删除后自下而上逐层判断是否需要调整。先向两边的兄弟借,两边的兄弟也没多的就向父亲借,逐层借到根节点

源代码

#include <stdio.h>
#include <malloc.h>
#include <math.h>
#include <stdlib.h>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
#define MAXM 10//定义B-树的最大的阶数(初始化数组用) 
typedef struct node{//B-树结点类型定义
    int keynum;//结点当前拥有的关键字的个数
    int key[MAXM];//key[1..keynum]存放关键字,key[0]不用
    struct node *parent;//父结点指针
    struct node *ptr[MAXM];//孩子结点指针数组ptr[0..keynum]
} BTNode;
typedef struct{//单次查找结果类型
    BTNode *pt;//指向找到的结点
    int i;//1..m,在结点中的关键字序号
    int tag;//1:查找成功,O:查找失败
}  Result;
int m;//m阶B-树
int Max;//m阶B-树中每个结点的至多关键字个数,Max=m-1
int Min;//m阶B-树中非叶子结点的至少关键字个数,Min=(m-1)/2
int Search(BTNode *p,int k);
Result SearchBTree(BTNode *t,int k);
void Insert(BTNode *&q,int i,int x,BTNode *ap);
void Split(BTNode *&q,BTNode *&ap);
void NewRoot(BTNode *&t,BTNode *p,int x,BTNode *ap);
void InsertBTree(BTNode *&t, int k, BTNode *q, int i);
void DispBTree(BTNode *t);
void Remove(BTNode *p,int i);
void Successor(BTNode *p,int i);
void MoveRight(BTNode *p,int i);
void MoveLeft(BTNode *p,int i);
void Combine(BTNode *p,int i);
void Restore(BTNode *p,int i);
int SearchNode(int k,BTNode *p,int &i);
int RecDelete(int k,BTNode *p);
void DeleteBTree(int k,BTNode *&root);
void DrawLine(Mat img, Point start, Point end);//利用opencv的函数画线
void show(BTNode *T);//用图形展示树 

int main(){
    BTNode *t=NULL;
    Result s;
    int a[10],i,j,n=10;
    for(i=0;i<n;a[i++]=rand()%100);
    m=3;//3阶B-树
    Max=m-1;
    Min=(m-1)/2;
    printf("创建一棵%d阶B-树:\n",m);
    for (j=0;j<n;j++){//创建
        s=SearchBTree(t,a[j]);
        if (s.tag==0)
            InsertBTree(t,a[j],s.pt,s.i);
        printf("   第%d步,插入%d: ",j+1,a[j]);
        DispBTree(t);
        //show(t); 
        printf("\n");
    }
    while(1){	
		printf("\nb-树:\n");
		DispBTree(t);
		show(t);
		printf("\n\n\n\n输入1:添加点\n输入2:删除点\n输入3:查找点\n输入0:结束\n请输入:");
		scanf("%d",&n);
		switch(n){
		case 1:{
			printf("输入添加点的值:");
			scanf("%d",&i);
			s=SearchBTree(t,i);
    	    if (s.tag==0)
     	       InsertBTree(t,i,s.pt,s.i);
			break;
			}
		case 2:{
			printf("输入删除点的值:");
			scanf("%d",&i);
			DeleteBTree(i,t);
			break;
			}
		case 3:{
			printf("输入查找点的值:");
			scanf("%d",&i);
			s=SearchBTree(t,i);
        	if(s.tag==0){
				printf("该点不在树中\n");
			}
			else{
				printf("该点在树中\n");
			}
			break;
			}
		case 0:{
			printf("再见!");
			exit(0);
			}
		default:{}
		}
	}
    return 0;
}

void DrawLine(Mat img, Point start, Point end){
	int thickness = 2;
	int lineType = 8;
	line(img,
		start,//起点坐标
		end,//终点坐标
		Scalar(0, 255, 255),//黑色
		thickness,
		lineType);
}

void show(BTNode *T){//利用opencv的函数非递归遍历树并画图输出 
	if(T){
		int thickness = 1,font_face = cv::FONT_HERSHEY_COMPLEX,i=0,x[100],y[100],z[100],top=0,windowwidth=600,j,k,m;
		Mat image=Mat::zeros(3*windowwidth/2,3*windowwidth/2,CV_8UC3);
		double font_scale =0.8;
		char ch[20];
		BTNode *p;
		BTNode *t[100],*dd[100];
		dd[0]=T;t[0]=T;x[0]=3*windowwidth/4;y[0]=windowwidth/20;z[0]=1;
		while(top>=0){//只要栈非空
			p=t[top];
			top--;//弹栈
			for(j=0;j<i;j++){
				if(p==dd[j]){
					break;
				}
			}
			for(k=1,m=0;k<=p->keynum;k++){
				if(p->key[k]>9){
					ch[m++]=p->key[k]/10+'0';
					ch[m++]=p->key[k]%10+'0';
					ch[m++]=',';
				}
				else{
					ch[m++]=p->key[k]+'0';
					ch[m++]=',';
				}
			}
			ch[--m]='\0';
			cv::putText(image, ch, Point(x[j],y[j]), font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
			for(k=p->keynum,m=0;k>=0;k--){
				if(p->ptr[k]){
					i++;
					dd[i]=p->ptr[k];
					z[i]=z[j]+1;
					y[i]=y[j]+2*windowwidth/27;
					x[i]=x[j]+5*(k*windowwidth-p->keynum*windowwidth/2)/pow(3.0,z[i]);
					DrawLine(image,Point(x[j]+2*k*windowwidth/33,y[j]),Point(x[i]+windowwidth/33,y[i]-windowwidth/27));
					top++;//
					t[top]=p->ptr[k];//压栈
				}
			}
		}
		imshow("展示图",image);
		waitKey(0);
	}
}

int Search(BTNode *p,int k){//在p->key[1..keynum]中查找i,使得p->key[i]<=k<p->key[i+1]
    int i;
    for(i=0;i<p->keynum && p->key[i+1]<=k;i++);
    return i;
}

Result SearchBTree(BTNode *t,int k){
    /*在m阶t树t上查找关键字k,返回结果(pt,i,tag)。若查找成功,则特征值
     tag=1,指针pt所指结点中第i个关键字等于k;否则特征值tag=0,等于k的
     关键字应插入在指针Pt所指结点中第i和第i+1个关键字之间*/
    BTNode *p=t,*q=NULL;//p指向待查结点,q指向p的父亲
    int found=0,i=0;
    Result r;
    while(p&&!found){
        i=Search(p,k);//在p->key[1..keynum]中查找i,使得p->key[i]<=k<p->key[i+1]
        if (i>0 && p->key[i]==k)//找到待查关键字
            found=1;
        else{
            q=p;
            p=p->ptr[i];
        }
    }
    r.i=i;
    if(found==1) {//查找成功
        r.pt=p;
        r.tag=1;
    }
    else{//查找不成功,返回k的插入位置信息
        r.pt=q;
        r.tag=0;
    }
    return r;//返回k的位置或应插入位置
}

void Insert(BTNode *&q,int i,int x,BTNode *ap){//将x和ap分别插入到q->key[i+1]和q->ptr[i+1]中
    int j;
    for(j=q->keynum;j>i;j--){//后面的每个元素都后移 
        q->key[j+1]=q->key[j];
        q->ptr[j+1]=q->ptr[j];
    }
    q->key[i+1]=x;
    q->ptr[i+1]=ap;
    if(ap!=NULL) ap->parent=q;
    q->keynum++;
}

void Split(BTNode *&q,BTNode *&ap){//将结点q分裂成两个结点,前一半保留,后一半移入新生结点ap
    int i,s=(m+1)/2;
    ap=(BTNode *)malloc(sizeof(BTNode));//生成新结点ap
    ap->ptr[0]=q->ptr[s];//后一半移入ap
    for(i=s+1;i<=m;i++){
        ap->key[i-s]=q->key[i];
        ap->ptr[i-s]=q->ptr[i];
        if(ap->ptr[i-s])
            ap->ptr[i-s]->parent=ap;
    }
    ap->keynum=q->keynum-s;
    ap->parent=q->parent;
    for(i=0;i<=q->keynum-s;i++){//修改孩子指向父结点的指针
        if(ap->ptr[i]!=NULL) ap->ptr[i]->parent=ap;
    }
    q->keynum=s-1;//修改q的keynum
}

void NewRoot(BTNode *&t,BTNode *p,int x,BTNode *ap){//生成含信息(T,x,ap)的新的根结点t,原t和ap为子树指针
    t=(BTNode *)malloc(sizeof(BTNode));
    t->keynum=1;
    t->ptr[0]=p;
    t->ptr[1]=ap;
    t->key[1]=x;
    if(p!=NULL) p->parent=t;
    if(ap!=NULL) ap->parent=t;
    t->parent=NULL;
}

void InsertBTree(BTNode *&t,int k,BTNode *q,int i){//在树t上结点q的key[i]与key[i+1]之间插入关键字k。若导致结点过大,则进行分裂调整
    BTNode *ap=NULL;
    int finished=0,needNewRoot=0,s,x=k;
    if(q==NULL)//t是空树则生成仅含关键字k的根结点t
        NewRoot(t,NULL,k,NULL);
    else{
        while(!needNewRoot &&!finished){
            Insert(q,i,x,ap);//将x和ap分别插入到q->key[i+1]和q->ptr[i+1]
            if(q->keynum<=Max) finished=1;//插入完成
            else{//分裂结点q,将q->key[s+1..m],q->ptr[s..m]和q->recptr[s+1..m]移入新结点ap
                Split(q,ap);
				s=(m+1)/2;
                x=q->key[s];
                if (q->parent){//在双亲结点*q中查找x的插入位置
                    q=q->parent;
                    i=Search(q,x);
                }
                else needNewRoot=1;
            }
        }
        if(needNewRoot==1)//根结点已分裂为结点q和ap
            NewRoot(t,q,x,ap);//生成新根结点t,q和ap为子树指针
    }
}

void DispBTree(BTNode *t){//用字符的方法递归输出
    int i;
    if (t){
        printf("[");
        for (i=1; i<t->keynum; i++)
            printf("%d ",t->key[i]);
        printf("%d",t->key[i]);
        printf("]");
        if (t->keynum){
            if(t->ptr[0]) printf("(");//有子树
            for (i=0;i<t->keynum;i++){//对每个子树进行递归调用
                DispBTree(t->ptr[i]);
                if (t->ptr[i+1]!=NULL) printf(",");
            }
            DispBTree(t->ptr[t->keynum]);
            if(t->ptr[0]) printf(")");
        }
    }
}

void Remove(BTNode *p,int i){//从p结点删除key[i]和它的孩子指针ptr[i]
    int j;
	p->keynum--;
    for (j=i+1;j<p->keynum;j++){//前移后面的所有关键字及孩子指针
        p->key[j-1]=p->key[j];
        p->ptr[j-1]=p->ptr[j];
    }
}

void Successor(BTNode *p,int i){//查找该关键字后值最小的叶子结点的值并将值赋给该点 
    BTNode *q;
    for(q=p->ptr[i];q->ptr[0];q=q->ptr[0]);
    p->key[i]=q->key[1];//复制值
}

void MoveRight(BTNode *p,int i){//向左兄弟借
    int k;
    BTNode *t=p->ptr[i];
    for (k=t->keynum;k>0;k--){//将右兄弟中所有关键字移动一位
        t->key[k+1]=t->key[k];
        t->ptr[k+1]=t->ptr[k];
    }
    t->ptr[1]=t->ptr[0];//从父结点移动关键字到右兄弟中
    t->keynum++;
    t->key[1]=p->key[i];
    t=p->ptr[i-1];//将左兄弟中最后一个关键字移动到父结点中
    p->key[i]=t->key[t->keynum];
    p->ptr[i]->ptr[0]=t->ptr[t->keynum];
    t->keynum--;
}

void MoveLeft(BTNode *p,int i){//向右兄弟借 
    int k;
    BTNode *t=p->ptr[i-1];//把父结点中的关键字移动到左兄弟中
    t->keynum++;
    t->key[t->keynum]=p->key[i];
    t->ptr[t->keynum]=p->ptr[i]->ptr[0];
    t=p->ptr[i];//把右兄弟的第一个关键字移到父亲中
    p->key[i]=t->key[1];
    t->keynum--;
    for(k=1;k<=t->keynum;k++){//将右兄弟所有关键字前移一位
        t->key[k]=t->key[k+1];
        t->ptr[k]=t->ptr[k+1];
    }
}

void Combine(BTNode *p,int i){//将三个结点合并到一个结点
    int k;
    BTNode *l=p->ptr[i-1],*q=p->ptr[i];//l指向左结点,q指向右结点,删除q 
    l->keynum++;
    l->key[l->keynum]=p->key[i];
    l->ptr[l->keynum]=q->ptr[0];
    if(q->keynum){
    	for(k=1;k<=q->keynum;k++){//插入所有右结点的关键字
     	   	l->keynum++;
    	    l->key[l->keynum]=q->key[k];
   	     	l->ptr[l->keynum]=q->ptr[k];
   		}
	}
    for(k=i;k<p->keynum;k++){//父结点后面的关键字前移 
        p->key[k]=p->key[k+1];
        p->ptr[k]=p->ptr[k+1];
    }
    p->keynum--;
    free(q);//释放空右结点的空间
}

void Restore(BTNode *p,int i){//关键字删除后,调整B-树,找到一个关键字将其插入到p->ptr[i]中
    if(!i)//最左边的情况,向右兄弟借或向父亲借 
        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 SearchNode(int k,BTNode *p,int &i){//在结点p中找关键字为k的位置i,成功时返回1,否则返回0
    if (k<p->key[1]){//k小于*p结点的最小关键字时返回0
        i=0;
        return 0;
    }
    else{//在*p结点中查找
        i=p->keynum;
        while (k<p->key[i] &&i>1){
        	i--;
		}
        return(k==p->key[i]);
    }
}

int RecDelete(int k,BTNode *p){//一层层递归查找并删除k
    int i,found;
    if(!p){
    	return 0;
	}
    else{
        if((found=SearchNode(k,p,i))){//找到了k
            if (p->ptr[i-1]!=NULL){//若非叶子结点,则用其后面最小的叶子节点的值来代替它并删除叶子节点 
                Successor(p,i);//由叶子节点代替它
                RecDelete(p->key[i],p->ptr[i]);//删除叶子结点
            }
            else{
            	Remove(p,i);//从p结点中位置i处删除关键字
			} 
        }
        else{//没找到则递归查找并删除k
        	found=RecDelete(k,p->ptr[i]);
		} 
        if(p->ptr[i]){
        	if(p->ptr[i]->keynum<Min)//删除后关键字个数小于MIN则进行修复 
                Restore(p,i);
		}
        return found;
    }
}

void DeleteBTree(int k,BTNode *&root){//从B-树root中删除关键字k,若在一个结点中删除指定的关键字,不再有其他关键字,则删除该结点
    BTNode *p;
    if(!RecDelete(k,root))
        printf("   关键字%d不在B-树中\n",k);
    else if(!root->keynum){//释放空的root
        p=root;
        root=root->ptr[0];
        free(p);
    }
}//共418行

运行结果

在这里插入图片描述

总结

b-树的操作看起来简单,实际实现起来却比想象中复杂很多。。。插入时的分裂,删除时向父亲、兄弟借等操作都有点麻烦。这题写的时间还挺长的,也是在写的过程中进一步体会了b-树的各种操作。一个函数里不能int t,再char t[N],会导致间接寻址级别不同。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值