采用整数为顶点值和多叉链表为存储结构,实现抽象数据类型B树。
ADT BTNode{
数据对象:D是具有相同特性的数据元素的集合,称为节点集。
数据关系:
若D为空集,则称为空树;
(1)树中每个结点最多含有m棵子树;
(2)若根结点不是叶子结点,则至少有2个子树;
(3)除根结点之外的所有非终端结点至少有┌m/2┐棵子树;
(4)每个非终端结点中包含信息:(n,A0,K1,A1,K2,A2,…,Kn,An)。其中:
1)Ki(1<=i<=n)为关键字,且关键字按升序排序;
2)指针Ai(0<=i<=n)指向子树的根结点,Ai-1指向子树中所有结点的关键字均小于Ki,且大于Ki-1;
3)关键字的个数n必须满足:┌m/2┐-1<=n<=m-1。
(5)所有的叶子节点都在同一层,子叶结点不包含任何信息。
基本操作:
void creat_btree();
初始条件:树全局变量T
操作结果:构建一棵阶数为m,空的B树
void SearchBTree(BTree t,int k,result &r);
初始条件:树全局变量T存在,
操作结果:在m阶B树t上查找关键字k,返回(pt,i,tag);
void InsertBTree(BTree &t,int k,BTree q,int i);
初始条件:树全局变量T存在, p->pt指向T中某个结点
操作结果: 在B树T上结点p->pt的key[i]和key[i+1]之间插入关键字k
void DeleteBTree(BTree &p,int i,BTree &T);
初始条件:树T存在,p->pt指向T中某个结点
操作结果:删除B树T上结点p->pt的关键字k Status
void show_Btree(BTree &p);
初始条件:树T存在
操作结果:以括号表示法打印B树
void Destory(BTree &t);
初始条件:树T存在
操作结果:销毁B树,释放内存
}ADT BTNode
2.存储结构定义
/*************************************************************************
> File Name: d.cpp
> Author: MentalOmega
> Mail: 965194745@qq.com
> Created Time: 2016年11月5日21:23:30
> 参考网站:
http://blog.csdn.net/jesseshen/article/details/6643747
http://www.cnblogs.com/vincently/p/4526560.html
http://blog.csdn.net/quitepig/article/details/8041308
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define KeyType int
#define Record string
const int m = 3 ;
using namespace std;
typedef struct BTNode{
int keynum; //节点当前关键字个数
KeyType key[m+1]; //关键字数组,key[0]未用
struct BTNode *parent; //双亲结点指针
struct BTNode *ptr[m+1]; //孩子结点指针数组
Record *recptr[m+1];
BTNode(){
keynum=0;
parent=NULL;
for(int i=0;i<m+1;i++)
{
ptr[i]=NULL;
}
}
}BTNode,*BTree;
BTree T = NULL ;
typedef struct{
BTree pt; //指向找到的结点
int i; //1<=i<=m,在结点中的关键字位序
int tag; //1:查找成功,0:查找失败
}result; //B树的查找结果类型
3. 算法设计
int Search(BTree p,int k){ //在p->key[1..p->keynum]找k
int i=1;
while(i<=p->keynum&&k>p->key[i]) i++;
return i;
}
void SearchBTree(BTree t,int k,result &r){
//在m阶B树t上查找关键字k,用r返回(pt,i,tag).
//若查找成功,则标记tag=1,指针pt所指结点中第i个关键字等于k;
//否则tag=0,若要插入关键字为k的记录,应位于pt结点中第i-1个和第i个关键字之间
int i=0,found=0;
BTree p=t,q=NULL;//初始,p指向根节点,p将用于指向待查找结点,q指向其双亲
while(p!=NULL&&0==found){
i=Search(p,k);
if(i<=p->keynum&&p->key[i]==k) found = 1 ;
else{q=p;p=p->ptr[i-1];}//指针下移
}
if(1==found){//查找成功,返回k的位置p及i
r={p,i,1};
}
else{//查找不成功,返回k的插入位置q及i
r={q,i,0};
}
}
void split(BTree &q,int s,BTree &ap){//将q结点分裂成两个结点,前一半保留在原结点,后一半移入ap所指新结点
int i,j,n=q->keynum;
ap=(BTNode*)malloc(sizeof(BTNode));//生成新结点
ap->ptr[0]=q->ptr[s];
for(i=s+1,j=1;i<=n;i++,j++){ //后一半移入ap结点
ap->key[j]=q->key[i];
ap->ptr[j]=q->ptr[i];
}
ap->keynum=n-s;
ap->parent=q->parent;
for(i=0;i<=n-s;i++)
if(ap->ptr[i]!=NULL) ap->ptr[i]->parent=ap;
q->keynum=s-1;
}
void newRoot(BTree &t,BTree p,int x,BTree 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 Insert(BTree &q,int i,int x,BTree ap){//关键字x和新结点指针ap分别插到q->key[i]和q->ptr[i]
int j,n=q->keynum;
for(j=n;j>=i;j--){
q->key[j+1]=q->key[j];
q->ptr[j+1]=q->ptr[j];
}
q->key[i]=x;q->ptr[i]=ap;
if(ap!=NULL) ap->parent=q;
q->keynum++;
}
void InsertBTree(BTree &t,int k,BTree q,int i){
//在B树中q结点的key[i-1]和key[i]之间插入关键字k
//若插入后结点关键字个数等于b树的阶,则沿着双亲指针链进行结点分裂,使得t仍是m阶B树
int x,s,finished = 0,needNewRoot = 0;
BTree ap;
if(NULL==q) newRoot(t,NULL,k,NULL);
else{
x=k;ap=NULL;
while(0==needNewRoot&&0==finished){
Insert(q,i,x,ap);//x和ap分别插到q->key[i]和q->ptr[i]
if(q->keynum<m) finished=1;//插入完成
else{
s = (m+1)/2;split(q,s,ap);x=q->key[s];
if(q->parent!=NULL){
q=q->parent;i=Search(q,x);//在双亲结点中查找x的插入位置
}
else needNewRoot=1;
}
}
if(1==needNewRoot)//t是空树或者根结点已经分裂成为q和ap结点
newRoot(t,q,x,ap);
}
}
void Remove(BTree &p,int i)
{
int j,n=p->keynum;
for(j=i;j<n;j++){
p->key[j]=p->key[j+1];
p->ptr[j]=p->ptr[j+1];
}
p->keynum--;
}
void Successor(BTree &p,int i){//由后继最下层非终端结点的最小关键字代替结点中关键字key[i]
BTree child = p->ptr[i];
while(child->ptr[0]!=NULL) child=child->ptr[0];
p->key[i]=child->key[1];
p=child;
}
void Restore(BTree &p, int i,BTree &T) {//对B树进行调整
int j;
BTree ap=p->parent;
if(ap==NULL) //若调整后出现空的根结点,则删除该根结点,树高减1
{
T=p; //根结点下移
p=p->parent;
return ;
}
BTree lc,rc,pr;
int finished = 0 ,r=0;
while(!finished)
{
r=0;
while(ap->ptr[r]!=p) r++; //确定p在ap子树中的位置
if(r==0)
{
r++;
lc=NULL,rc=ap->ptr[r];
}
else if(r==ap->keynum)
{
rc=NULL;lc=ap->ptr[r-1];
}
else
{
lc=ap->ptr[r-1];rc=ap->ptr[r+1];
}
if(r>0&&lc!=NULL&&(lc->keynum>(m-1)/2))//向左兄弟借关键字
{
p->keynum++;
for(j=p->keynum;j>1;j--)//结点关键字右移
{
p->key[j]=p->key[j-1];
p->ptr[j]=p->ptr[j-1];
}
p->key[1]=ap->key[r];//父亲插入到结点
p->ptr[1]=p->ptr[0];
p->ptr[0]=lc->ptr[lc->keynum];
if(NULL!=p->ptr[0])//修改p中的子女的父结点为p
{
p->ptr[0]->parent=p;
}
ap->key[r]=lc->key[lc->keynum];//左兄弟上移到父亲位置
lc->keynum--;
finished=1;
break;
}
else if(ap->keynum>r&&rc!=NULL&&(rc->keynum>(m-1)/2)) //向右兄弟借关键字
{
p->keynum++;
p->key[p->keynum]=ap->key[r]; //父亲插入到结点
p->ptr[p->keynum]=rc->ptr[0];
if(NULL!=p->ptr[p->keynum]) //修改p中的子女的父结点为p
p->ptr[p->keynum]->parent=p;
ap->key[r]=rc->key[1]; //右兄弟上移到父亲位置
rc->ptr[0]=rc->ptr[1];
for(j=1;j<rc->keynum;j++) //右兄弟结点关键字左移
{
rc->key[j]=rc->key[j+1];
rc->ptr[j]=rc->ptr[j+1];
}
rc->keynum--;
finished=1;
break;
}
r=0;
while(ap->ptr[r]!=p) //重新确定p在ap子树的位置
r++;
if(r>0&&(ap->ptr[r-1]->keynum<=(m-1)/2)) //与左兄弟合并
//if(r>0) //与左兄弟合并
{
lc=ap->ptr[r-1];
p->keynum++;
for(j=p->keynum;j>1;j--) //将p结点关键字和指针右移1位
{
p->key[j]=p->key[j-1];
p->ptr[j]=p->ptr[j-1];
}
p->key[1]=ap->key[r]; //父结点的关键字与p合并
p->ptr[1]=p->ptr[0]; //从左兄弟右移一个指针
ap->ptr[r]=lc;
for(j=1;j<=lc->keynum+p->keynum;j++) //将结点p中关键字和指针移到p左兄弟中
{
lc->key[lc->keynum+j]=p->key[j];
lc->ptr[lc->keynum+j]=p->ptr[j];
}
if(p->ptr[0]) //修改p中的子女的父结点为lc
{
for(j=1;j<=p->keynum;j++)
if(p->ptr[p->keynum+j]) p->ptr[p->keynum+j]->parent=lc;
}
lc->keynum=lc->keynum+p->keynum; //合并后关键字的个数
for(j=r;j<ap->keynum;j++)//将父结点中关键字和指针左移
{
ap->key[j]=ap->key[j+1];
ap->ptr[j]=ap->ptr[j+1];
}
ap->keynum--;
pr=p;free(pr);
pr=NULL;
p=lc;
}
else //与右兄弟合并
{
rc=ap->ptr[r+1];
if(r==0)
r++;
p->keynum++;
p->key[p->keynum]=ap->key[r]; //父结点的关键字与p合并
p->ptr[p->keynum]=rc->ptr[0]; //从右兄弟左移一个指针
rc->keynum=p->keynum+rc->keynum;//合并后关键字的个数
ap->ptr[r-1]=rc;
for(j=1;j<=(rc->keynum-p->keynum);j++)//将p右兄弟关键字和指针右移
{
rc->key[p->keynum+j]=rc->key[j];
rc->ptr[p->keynum+j]=rc->ptr[j];
}
for(j=1;j<=p->keynum;j++)//将结点p中关键字和指针移到p右兄弟
{
rc->key[j]=p->key[j];
rc->ptr[j]=p->ptr[j];
}
rc->ptr[0]=p->ptr[0]; //修改p中的子女的父结点为rc
if(p->ptr[0])
{
for(j=1;j<=p->keynum;j++)
if(p->ptr[p->keynum+j]) p->ptr[p->keynum+j]->parent=rc;
}
for(j=r;j<ap->keynum;j++)//将父结点中关键字和指针左移
{
ap->key[j]=ap->key[j+1];
ap->ptr[j]=ap->ptr[j+1];
}
ap->keynum--;//父结点的关键字个数减1
pr=p;
free(pr);
pr=NULL;
p=rc;
}
ap=ap->parent;
if(p->parent->keynum>=(m-1)/2||(NULL==ap&&p->parent->keynum>0))
finished=1;
else if(ap==NULL) //若调整后出现空的根结点,则删除该根结点,树高减1
{
pr=T;
T=p; //根结点下移
free(pr);
pr=NULL;
finished=1;
}
p=p->parent;
}
}
void DeleteBTree(BTree &p,int i,BTree &T){//删除B树上p结点的第i个关键字
if(p->ptr[i]!=NULL){//若不是在最下层非终端结点
Successor(p,i);//在Ai子树中找出最下层非终端结点的最小关键字替代ki
DeleteBTree(p,1,T);//转换为删除最下层非终端结点的最小关键字
}else{//若是最下层非终端结点
Remove(p,i);
if(p->keynum<(m-1)/2)//删除后关键字个数小于(m-1)/2
Restore(p,i,T);//调整B树
}
}
void show_Btree(BTree &p)
{
if(p==NULL) {puts("B tree does not exist");return ;}
bool have_child = false ;
printf("[");
for(int i=1;i<=p->keynum;i++)
{
if(i==1) ;
else printf(" ");
printf("%d",p->key[i]);
}
printf("]");
for(int i=0;i<=p->keynum;i++)
{
if(p->ptr[i]!=NULL)
{
if(i==0) printf("<");
else printf(",");
show_Btree(p->ptr[i]);
have_child = true ;
}
}
if(have_child) printf(">");
}
void show_Btree2(BTree &p,int deep)
{
if(p==NULL) {return ;}
int i ;
for(i = 0 ; i < p->keynum ;i++)
{
show_Btree2(p->ptr[i],deep+1);
for(int i=0;i<deep;i++)
{
printf("\t");
}
printf("%d\n",p->key[i+1]);
}
show_Btree2(p->ptr[i],deep+1);
}
void Destory(BTree &t)
{
int i = 0 ;
if(t!=NULL)
{
while(i<t->keynum)
{
Destory(t->ptr[i]);
free(t->ptr[i]);
i++;
}
}
free(t);
t=NULL;
}
void creat_btree()
{
T = new BTNode ;
T->keynum=0;
puts("New success");
}
void insert_keytype()
{
puts("Enter an element to be inserted");
KeyType temp;
scanf("%d",&temp);
result p ;
SearchBTree(T,temp,p);
if(p.tag==0)
{
InsertBTree(T,temp,p.pt,p.i);
puts("Insert success");show_Btree(T);
puts("");
}
else puts("The element is already in the B tree.");
}
void find_keytype()
{
puts("Enter an element to find");
KeyType temp;
scanf("%d",&temp);
result p ;
SearchBTree(T,temp,p);
if(p.tag)
{
puts("Find success");
//cout<<p.pt->recptr[p.i]<<endl;
}
else puts("Lookup failure");
}
void delete_keytype()
{
puts("Enter an element to be deleted");
KeyType temp;
scanf("%d",&temp);
result p ;
SearchBTree(T,temp,p);
if(p.tag)
{
DeleteBTree(p.pt,p.i,T);
puts("Delete success");show_Btree(T);
puts("");
}
else puts("The element does not exist in the B tree.");
}
4.测试
int main()
{
//freopen("in.txt","r",stdin);
puts("*************************************************************************");
puts("> File Name: d.cpp");
puts("> Author: MentalOmega");
puts("> Created Time: 2016.11.5 21:23:30");
puts("> Reference material:");
puts("http://blog.csdn.net/jesseshen/article/details/6643747");
puts("http://www.cnblogs.com/vincently/p/4526560.html");
puts("http://blog.csdn.net/quitepig/article/details/8041308");
puts("*************************************************************************");
int order=0;
while(order!=7)
{
puts("---------------------------------------------------------------------");
puts("Select menu");
puts("1:Creat a new 3-B-tree");
puts("2:Insert element");
puts("3:Find element");
puts("4:Delete element");
puts("5:Print B-tree");
puts("6:exit");
cin>>order;
switch(order)
{
case 1 : {Destory(T);creat_btree();break;}
case 2 : {insert_keytype();break;}
case 3 : {find_keytype();break;}
case 4 : {delete_keytype();break;}
case 5 : {show_Btree(T);puts("");break;}
case 6 : {return 0;}
}
}
/*调试用代码
BTree T = new BTNode ;
T->keynum=0;
result p ;
//int in[]={15,30,50,10,25,45,60,80,90,75};
int in[]={35,16,18,70,5,50,22,60,13,17,12,45,25,42,15,90,30,7};
//int in[] = {35,25,22,30,60,42,70};
for(int i=0;i<18;i++)
{
printf("insert %d ",in[i]);
SearchBTree(T,in[i],p);
InsertBTree(T,in[i],p.pt,p.i);
show_Btree(T);
puts("");
}
//int out[]={30,50,25,40};
int out[]={45,90,50,22,42};
//int out[]={22};
for(int i=0;i<5;i++)
{
SearchBTree(T,out[i],p);
if(p.tag)
{
printf("delete %d ",out[i]);
DeleteBTree(p.pt,p.i,T);
show_Btree(T);
puts("");
}
else
{
printf("%d is not in the tree\n",out[i]);
}
}
//Destory(T);
//if(T) show_Btree(T);
//else printf("B-tree is empty\n");
show_Btree2(T,0);
*/
return 0;
}
测试结果: 创建一棵新的3阶B树
3阶B树插入示例
3阶B树删除情形1
3阶B树删除情形2
3阶B树删除情形3
小测试样例
大测试样例
5.实验总结与体会
1)对于这次实验,我是抱着挑战自己的心态来做的,因为自己对于树的数据结构不够熟练,所以觉得应该选做一些自己不会的实验,这样就能在做实验的过程中学习到很多有用的,自己还不会的东西。简直是一举两得。
2)M阶B树可以说跟二叉树比起来难了很多,因为B树结点数目比较多,要考虑到的操作也是比较多的,光是删除操作就分终端节点跟非终端结点,并且在调整B树(Successor)的情形之中又有5种情形需要考虑,所以在正式打代码之前,我都是先认认真真了解B树的基本原理跟细节操作之后,参考了很多资料之后,再动手打代码,并且先选择了较为简单的3阶b树作为实验,先易后难,这让我在打代码的时候知道自己每一步打的都是些什么,减少了出错的机会,并且在重要的部分加上注释的方法,让我在后期测试数据遇到问题调试的时候给了很大的便利。
3)本次编程的环境用到的是codeblock,其调试功能十分完善,助我一臂之力,在实现B树基本界面的同时还增加了一段测试代码,主要用于批量插入删除,这段测试代码帮了我很大的忙,避免了调试过程中繁琐的读入读出数据,保证了数据读入的准确性。
4)实验过程中,对于B树的表现形式考虑了相当多种表示方法,尝试过凹入表,但是觉得表现效果不够好,最后觉得是数据结构中广义表的括号表示法非常适合表现B树,所以采用的是括号表示法来打印B树。最后一组大数据测试样例中就是由小到大的凹入表跟括号表示法的对比。
5)在实验过程中跟一些一同做B树的同学们的细致交流,让我对此次实验有了更多的深刻体会。并且他们还给了我很多测试的数据,这让我能够从输出样例的过程中找到代码的错误之处。