任务简述
设计并实现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],会导致间接寻址级别不同。