这里只用关键字来代替实际的外存文件。
B树又叫平衡多路查找树,是一种组织和维护外存文件系统非常有效的数据结构
一棵m阶的B树(m叉树)的特性如下:
树中每个结点最多含有m个孩子(m>=2);
除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]=(m-1)/2个孩子(其中ceil(x)是一个取上限的函数);
若根结点不是叶子结点,则至少有2个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点);
所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部接点或查询失败的接点,
实际上这些结点不存在,指向这些结点的指针都为null);
每个非终端结点中包含有n个关键字信息: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:
a) Ki (i=1...n)为关键字,且关键字按顺序升序排序K(i-1)< Ki。
b) Pi为指向子树根的接点,且指针P(i-1)指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)。
c) 关键字的个数n必须满足: (m-1)/2<= n <= m-1
/*
* B树的插入算法
*将关键字K插入到B树中分两步进行:
(1)、利用前述的B树的查找算法找出该关键字的插入结点(插入结点一定是叶子结点)
(2)、判断该结点是否有空位置,即判断该结点是有满足n<m-1;若满足,则说明还有空位置
直接插入即可(要保持该结点的关键字仍然有序);
若n=m-1,说明该结点已经没有空位置,要将该结点分裂成两个.分裂的做法是,取一个
新结点,把原结点上的关键字和k按升序排列,从中间位置((m+1)/2处)把关键字(
不包含中间关键字)分裂成两部分,左部分所含关键字放在旧节点中,右部分所含关键
字放在新结点中,中间关键字连同新结点的地址插入到父节点中。
如果父节点的关键字数也满了,那就向上继续分裂,最多是做到把根节点也分裂,产生
新的根节点。
*/
/*
*B树的删除算法
*要使得删除后的结点满足n>=(m-1)/2,将涉及到结点的"合并"问题。
*在B树中删除关键字K的过程分两步完成:
(1)、查找出该关键字所在结点
(2)、在结点上删除关键字k分两种情况:一是在叶子结点上删除、二在非叶子结点上删除
1)、在非叶子结点上删除关键字k的过程:假设要删除的关键字key[i],在删去该关键
字后,以该结点的ptr[i]所指向的子树中的最小关键字key[min]来代替被删关键
字key[i]所在的位置(注意ptr[i]所指子树中的最小关键字key[min]一定是在叶子
节点上),然后再也指针ptr[i]所指结点为根节点查找并删除key[min](即以ptr[i]
所指的结点为B树的根节点,key[min]为删除结点,然后调用B树的删除算法),这样
也就把在非叶子结点上删除关键字k的问题转化为在叶子结点上面删除关键字key[min]的问题。
2)、在叶子结点上删除关键字共分以下三种情况:
NO.1:如果被删除的结点的关键字数大于(m-1)/2,说明删去该关键字后,该结点仍满
足B树的定义,可以直接删除.
NO.2:如果被删除的结点的关键字数等于(m-1)/2,说明删去该节点后关键字数将不满足
B树的定义,此时若该结点的左(或右)兄弟结点的关键字树大于(m-1)/2,则把该
结点的左(或右)兄弟结点中最大(或最小)的关键字上移到双亲结点中,同时把双
亲结点中大于(或小于)上移关键字的关键字下移到要删除的关键字结点中,这样
删去该关键字后,该结点的以及它的左(或右)兄弟仍满足B树的定义.
NO.3:如果被删除的结点的关键字数等于(m-1)/2,并且该结点的左右兄弟(如果存在的话)的关键字数目
也都为(m-1)/2,这时,需要删除关键字的结点与其左(或右)兄弟结点以及双亲结点中分割二者的
关键字合并成一个新的结点。如果因此双亲结点中的关键字数小于(m-1)/2,则对双亲节点做同样
的处理,以至于可能对根结点做同样的处理以至于整棵树减少一层。
*/
文件"btree.h"
#include <iostream>
#include <queue>
using namespace std;
const int MAX_M=10; //B树的最大阶
typedef int KeyType; //关键字类型
class BTree;
//B树结点类
class BTNode
{
friend BTree;
private:
int keynum; //结点当前拥有的关键字数目
KeyType key[MAX_M]; //m阶B树每个结点最多有m-1个关键字,
//所以key[0]不放数据,从key[1]开始放
BTNode *parent; //指向父节点
BTNode *ptr[MAX_M]; //孩子结点指针数组,m阶B树最多可以有m个孩子结点
public:
int Get_Num()
{
return keynum;
}
void Print_Keys()
{
for(int i=1;i<=keynum;i++)
cout<<key[i]<<" ";
cout<<endl;
}
};
//用于搜索时返回结果用
struct Result
{
BTNode *pt; //指向找到的结点
int i; //找到的关键字在结点中的序号1,...,m
int tag; //结果标志,1:找到 0:未找到
};
class BTree
{
private:
int m; //B树的阶数
int Max; //B树结点最多关键字数目 Max=m-1
int Min; //B树结点最少关键字数目 Min=(m-1)/2
public:
BTree():m(0),Max(0),Min(0)
{
}
//初始化B树的三个参数
void Init(int newm)
{
m=newm;
Max=newm-1;
Min=Max/2;
}
//B树查找算法
Result Search_BTree(BTNode *root,KeyType k)
{
BTNode *p=root,*q=NULL; //q指向p的父节点
int found=0,i=0;
Result r;
while(p && found == 0 )
{
i=Search(p,k);
//在p->key[1,..,keynum]中查找k,使得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
{
//查找不成功,q指向可插入k的结点
r.pt=q;
r.tag=0;
}
return r;
}
//辅组查找函数
int Search(BTNode *p,KeyType k)
{
//在p->key[1,..,keynum]中查找k,使得p->key[i] <= k < p->key[i+1]
for(int i=0 ; i<p->keynum && p->key[i+1] <= k ; i++);
return i;
}
//插入函数
void Insert_BTree(BTNode *&root,KeyType k,BTNode *q,int i)
{
//在树的*q结点的key[i]和key[i+1]之间插入关键字k
//如果插入过程中,结点关键字数目超出范围,则沿着
//双亲链进行必要的结点分裂调整,使树root仍为m阶树
//q与i都是由搜索函数所返回的Result结构体中的信息
BTNode *ap;
int finished,NeedNewRoot,s;
KeyType x;
if(q == NULL)
NewRoot(root,NULL,k,NULL); //树为空,生成仅含关键字k的根节点root
else
{
x=k;
ap=NULL;
finished=NeedNewRoot=0;
while(NeedNewRoot == 0 && finished == 0)
{
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]
//移到新结点*ap
s=(m+1)/2;
Split(q,ap);
x=q->key[s];
if(q->parent)
{
//在双亲结点中查找x的插入位置
q=q->parent;
i=Search(q,x);
}
else
NeedNewRoot=1;
}
}
if(NeedNewRoot == 1) //根节点已经分裂成*q和*ap,生成新的根节点root,q和ap为子树指针
NewRoot(root,q,x,ap);
}
}
//辅组插入函数
void Insert(BTNode *&q,int i,KeyType x,BTNode *ap)
{
//将x和ap插入到q->key[i+1]和q->ptr[i+1]
int j;
for(j=q->keynum; j>i ; j--)
{
//空出一个位置来放x与ap
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)
ap->parent=q;
q->keynum++;
}
//分裂函数
void Split(BTNode *&q,BTNode *&ap)
{
//将结点*q分裂成两个部分,前一半保留,后一半移入新结点*ap
int i,s=(m+1)/2;
ap=new BTNode;
ap->ptr[0]=q->ptr[s];
for(i=s+1;i<=m;i++)
{
ap->key[i-s]=q->key[i];
ap->ptr[i-s]=q->ptr[i];
}
ap->keynum=q->keynum-s;
ap->parent=q->parent;
for(i=0;i<=q->keynum-s;i++)
if(ap->ptr[i])
ap->ptr[i]->parent=ap;
q->keynum=s-1;
}
//生成新的根节点
void NewRoot(BTNode *&root,BTNode *p,KeyType x,BTNode *ap)
{
root=new BTNode;
root->keynum=1;
root->ptr[0]=p;
root->ptr[1]=ap;
root->key[1]=x;
if(p)
p->parent=root;
if(ap)
ap->parent=root;
root->parent=NULL;
}
//通过组个插入的方法来创建一棵B树
BTNode *Create_BTree(KeyType a[],int n)
{
Result r;
BTNode *root=NULL;
int i;
for(i=0;i<n;i++)
{
r=Search_BTree(root,a[i]);
if(r.tag == 0)
Insert_BTree(root,a[i],r.pt,r.i);
}
return root;
}
//按照深度优先的层次顺序输出每个结点的关键字值
void Print(BTNode *root,int layer,int num)
{
int i;
BTNode *p=root;
if(root == NULL)
return;
else
{
cout<<"第 "<<layer<<" 层第 "<<num<<" 个结点:";
for(i=1;i<=p->keynum;i++)
cout<<p->key[i]<<" ";
cout<<endl;
cout<<endl;
num=1;
for(i=0;i<=p->keynum;i++)
{
if(p->ptr[i])
Print(p->ptr[i],layer+1,num++);
}
}
}
//在树中删除关键字k
int Delete_BTree(BTNode *&root,KeyType k)
{
BTNode *p; //用于释放一个空的root
if(RecDelete(root,k) == 0)
{
cout<<"关键字 "<<k<<" 不存在B树中"<<endl;
return 0;
}
else if(root->keynum == 0)
{
//根节点为空
p=root;
root=root->ptr[0];
delete p;
}
return 1;
}
int RecDelete(BTNode *p,KeyType k)
{
//查找并删除关键字
int i,found;
if(!p)
return 0;
else
{
if( (found=SearchNode(p,k,i)) == 1)
{
if(p->ptr[i-1]) //非叶子结点
{
Successor(p,i); //找后代中最小的那个来代替它
RecDelete(p->ptr[i],p->key[i]);
}
else
Remove(p,i); //在结点*p的位置i删除关键字
}
else
found=RecDelete(p->ptr[i],k);
if(p->ptr[i])
if(p->ptr[i]->keynum < Min)
Restore(p,i); //删除后关键字数目少于MIN,需要进行合并处理
return found;
}
}
int SearchNode(BTNode *p,KeyType k,int &i)
{
//搜索关键字在结点p中的位置
if(k<p->key[1])
{
i=0;
return 0;
}
else
{
i=p->keynum;
while(k < p->key[i] && i>1)
i--;
return(k == p->key[i]);
}
}
void Remove(BTNode *p,int i)
{
//在结点*p的位置i删除关键字
int j;
for(j=i+1;j<=p->keynum;j++)
{
p->key[j-1]=p->key[j];
p->ptr[j-1]=p->ptr[j];
}
p->keynum--;
}
void Successor(BTNode *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 Restore(BTNode *p,int 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);
}
void MoveRight(BTNode *p,int i)
{
//将p->ptr[i-1]中的一个关键字移动到双亲中,双亲一个关键字移动到p->ptr[i]中
int c;
BTNode *t=p->ptr[i];
for(c=t->keynum;c>0;c--)
{
t->key[c+1]=t->key[c];
t->ptr[c+1]=t->ptr[c];
}
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)
{
//将p->ptr[i]中的一个关键字移动到双亲中,双亲一个关键字移动到p->ptr[i-1]中
int c;
BTNode *t;
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->ptr[0]=t->ptr[1];
t->keynum--;
for(c=1;c<=t->keynum;c++)
{
t->key[c]=t->key[c+1];
t->ptr[c]=t->ptr[c+1];
}
}
void Combine(BTNode *p,int i)
{
//将结点*p的左右孩子和父节点的第一个关键字结合,然后去掉右孩子
int c;
BTNode *q=p->ptr[i];
BTNode *l=p->ptr[i-1];
l->keynum++;
l->key[l->keynum]=p->key[i];
l->ptr[l->keynum]=q->ptr[0];
for(c=1;c<=q->keynum;c++)
{
l->keynum++;
l->key[l->keynum]=q->key[c];
l->ptr[l->keynum]=q->ptr[c];
}
for(c=i;c<p->keynum;c++)
{
p->key[c]=p->key[c+1];
p->ptr[c]=p->ptr[c+1];
}
p->keynum--;
delete q;
}
};
测试文件
#include "btree.h"
#include <iostream>
using namespace std;
int main()
{
BTree tree;
BTNode *root=NULL;
Result r;
int m,n,pos;
KeyType temp;
//KeyType *a;
KeyType a[20]={1,2,6,7,11,4,8,13,10,5,17,9,16,20,3,12,14,18,19,15};
cout<<"输入你要建立的B-树的阶数(3-10): ";
cin>>m;
while(m<3 ||m>10)
{
cout<<"输入的阶数不满足要求(3-10),请重新输入:";
cin>>m;
}
tree.Init(m);
/*
cout<<"输入要初始化B树的数组的大小:";
cin>>n;
a=new KeyType[n];
cout<<"输入 "<<n<<" 个关键字数据:"<<endl;
for(int i=0;i<n;i++)
cin>>a[i];
*/
n=20;
root=tree.Create_BTree(a,n);
cout<<endl;
cout<<"B树的每个结点的结构如下,每个结点一行,按照从上到下从左到右的顺序"<<endl;
cout<<endl;
tree.Print(root,1,1);
cout<<"输入你要查找的关键字(输入-1退出):"<<endl;
while(cin>>temp)
{
if(temp == -1)
break;
r=tree.Search_BTree(root,temp);
if(r.tag == 1)
{
cout<<endl;
cout<<"关键字在B树中,所在结点的关键字总数是:"<<r.pt->Get_Num()<<endl;
cout<<"该结点所有关键字为:";
r.pt->Print_Keys();
cout<<endl;
}
else
cout<<"该关键字不在B-树中"<<endl;
}
cout<<"输入你要删除的关键字(输入-1退出):"<<endl;
while(cin>>temp)
{
if(temp == -1)
break;
else
{
temp=tree.Delete_BTree(root,temp);
if(temp == 1)
{
cout<<"删除结点后的树的结构如下:"<<endl;
tree.Print(root,1,1);
}
cout<<endl;
}
}
return 0;
}
运行结果:
输入你要建立的B-树的阶数(3-10): 5
B树的每个结点的结构如下,每个结点一行,按照从上到下从左到右的顺序
第 1 层第 1 个结点:10
第 2 层第 1 个结点:3 6
第 3 层第 1 个结点:1 2
第 3 层第 2 个结点:4 5
第 3 层第 3 个结点:7 8 9
第 2 层第 2 个结点:13 16
第 3 层第 1 个结点:11 12
第 3 层第 2 个结点:14 15
第 3 层第 3 个结点:17 18 19 20
输入你要查找的关键字(输入-1退出):
8
关键字在B树中,所在结点的关键字总数是:3
该结点所有关键字为:7 8 9
12
关键字在B树中,所在结点的关键字总数是:2
该结点所有关键字为:11 12
-1
输入你要删除的关键字(输入-1退出):
12
删除结点后的树的结构如下:
第 1 层第 1 个结点:3 6 10 16
第 2 层第 1 个结点:1 2
第 2 层第 2 个结点:4 5
第 2 层第 3 个结点:7 8 9
第 2 层第 4 个结点:11 13 14 15
第 2 层第 5 个结点:17 18 19 20
8
删除结点后的树的结构如下:
第 1 层第 1 个结点:3 6 10 16
第 2 层第 1 个结点:1 2
第 2 层第 2 个结点:4 5
第 2 层第 3 个结点:7 9
第 2 层第 4 个结点:11 13 14 15
第 2 层第 5 个结点:17 18 19 20
9
删除结点后的树的结构如下:
第 1 层第 1 个结点:3 6 11 16
第 2 层第 1 个结点:1 2
第 2 层第 2 个结点:4 5
第 2 层第 3 个结点:7 10
第 2 层第 4 个结点:13 14 15
第 2 层第 5 个结点:17 18 19 20
-1
Press any key to continue
生成的5阶B树