B-树的算法实现最难的还是插入操作和删除操作。就连生成B-树也依赖着插入操作。InsertBTree函数比较好的解决方法还是模块化- 将插入之后结点的数目变化分情况讨论,将复杂的逻辑结构拆分成 1、插入结点操作Insert 2、结点分裂操作Split 3、寻找插入相应的位置的操作Search 4、建立新的根节点的操作NewRoot 四个函数,具体应用到各种情况的讨论中。
删除函数需要注意的是指针域的挪动一个都不能马虎。因为连锁反应引起的删除虚拟结点并不是最后一层非终端结点。需要仔仔细细一个数据都不能漏。
此外由于根节点不是叶子结点(NULL)则至少需要两颗子树。不同于其他非终端结点需要(m/2)上限个,因此时时刻刻都需要单独讨论。
主要参考:http://www.cnblogs.com/kangjianwei101/p/5221816.html 自己根据理解加上了一些注释,略有微小的改动。
#ifndef B_TREE_H
#define B_TREE_H
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "Base.h"
//B-Tree
//宏定义
#define M 3 //B树的阶
#define MAX_LEVEL 100
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
//类型定义
typedef ElemType_Search BTElemType;
typedef struct BTNode
{
int keynum; //结点中的关键字个数
struct BTNode *parent; //指向双亲结点
KeyType key[M+1]; //关键字向量,0号单元未用
struct BTNode *ptr[M+1]; //子树的指针向量
}BTNode; //B树的结点
typedef BTNode *BTree;
//查找结果类型
typedef struct
{
BTree pt; //指向找到的结点
int i; //关键字在结点中的序号,即插入位置
int tag; //1查找成功0查找失败
}Result;
//函数列表
Status CreateBTree (BTree *BT, Table T);
Result SearchBTree (BTree BT, KeyType k);
int Search (BTree p, KeyType k);
//返回k在结点p中的次序,若不存在返回0
Status InsertKey (BTree *BT, KeyType k);
//将关键字k插入树
Status InsertBTree (BTree *BT, KeyType k, BTree q, int i);
//插入算法,在结点*q的key[i]和key[i+1]之间插入关键字k
void Insert (BTree q, KeyType i, KeyType x, BTree ap);
//将x和ap分别插入到q->key[i+1]和q->ptr[i+1]中
void split (BTree q, int s, BTree *ap);
//以s为界,将q指向的结点分裂成q和ap指向的两部分
void NewRoot (BTree *BT, BTree q, int x, BTree ap);
//生成含信息(BT,x,ap)的新的根节点*BT,原BT和ap为子树指针,q初始值为NULL
Status DeleteKey (BTree *BT, KeyType k);
//从B树中删除关键字k
Status DeleteBTree (BTree *BT, BTree q, int i);
//从B树中删除结点q中的第i个关键字
void Delete (BTree *BT, BTree q, int i);
//从B树中删除结点q中的第i个关键字
Status SearchMinKey (BTree BT, Result *R);
//找出BT中最小的关键字
Status FoundParent (BTree q, BTree *p, int *order);
//寻找双亲结点,q为p的第i个孩子
Status LeftMove (BTree old_ptr, int m, BTree new_ptr, int n, int len);
//向左移动关键字和指针
Status RightMove (BTree old_ptr, int m, BTree new_ptr, int n, int len);
//向右移动关键字和指针
void PrintBT_Level (BTree BT);
//层序输出B树
void PrintBT_InOrder (BTree BT);
//中序输出B树
#endif // B_TREE_H
#ifndef B_TREE_C
#define B_TREE_C
#include "B-Tree.h"
//函数列表
Status CreateBTree (BTree *BT, Table T)
{
int i;
*BT = NULL;
if (T.length){
for (i=1;i<=T.length;i++){
if (!InsertKey(BT,T.elem[i].key))
break;
}
}
if (i>T.length)
return OK;
else
return ERROR;
}
Result SearchBTree (BTree BT, KeyType k)
{
Result R = {NULL,0,0};
BTree p,q;
int found,i;
p = BT;
q = NULL;
found = FALSE;
i=0;
while (p && !found){
i = Search(p,k);
if (i>0 &&p->key[i]==k)
found = TRUE;
else {
q = p;
p = p->ptr[i];
}
}
R.i = i;
if (found){
R.pt = p;
R.tag = 1;
} else {
R.pt = q;
R.tag = 0;
}
return R;
}
int Search (BTree p, KeyType k)
{
int i,j;
for (i=0,j=1;j<=p->keynum;j++){
if (p->key[j]<=k)
i = j;
else
break;
}
return i;
}
//返回k在结点p中的次序,若不存在返回0
Status InsertKey (BTree *BT, KeyType k)
{
Result R;
R = SearchBTree(*BT,k);
if (R.tag==1)
return ERROR;
else {
InsertBTree(BT,k,R.pt,R.i);
}
return OK;
}
//将关键字k插入树
Status InsertBTree (BTree *BT, KeyType k, BTree q, int i)
{
KeyType x = k;
BTree ap = NULL;
int finished = FALSE;
int s;
while (q && !finished){
Insert(q,i,x,ap); //将x和ap分别插入到q中
if (q->keynum<M)
finished = TRUE;
else {
//关键字数目超出限制
s = ceil((double)M/2); //除根节点之外至少有s棵子树,s-1个关键字
split(q,s,&ap); //以s为界分q为q和ap
x = q->key[s];
q = q->parent;
if (q) //在双亲结点中寻找插入x的位置
i = Search(q,x);
}
}
if (!finished) //BT是空树q的初始值为NULL或者根结点分裂为*q *ap
NewRoot(BT,q,x,ap);
return OK;
}
//插入算法,在结点*q的key[i]和key[i+1]之间插入关键字k
void Insert (BTree q, KeyType i, KeyType x, BTree ap)
{
int j;
for (j=q->keynum;j>i;j--){
//注意key和ptr定义比上限多出一位
q->key[j+1] = q->key[j];
q->ptr[j+1] = q->ptr[j];
}
q->key[i+1] = x;
q->ptr[i+1] = ap;
q->keynum++;
}
//将x和ap分别插入到q->key[i+1]和q->ptr[i+1]中
void split (BTree q, int s, BTree *ap)
{
(*ap) = (BTree)malloc (sizeof(BTNode));
if (!(*ap))
exit (OVERFLOW);
(*ap)->ptr[0] = q->ptr[s];
int i;
for (i=s+1;i<=M;i++){
(*ap)->key[i-s] = q->key[i];
(*ap)->ptr[i-s] = q->ptr[i];
}
(*ap)->keynum = M-s;
q->keynum = s - 1;
(*ap)->parent = q->parent;
for(i=0;i<=(*ap)->keynum;i++){
if ((*ap)->ptr[i])
(*ap)->ptr[i]->parent = *ap;
}
}
//以s为界,将q指向的结点分裂成q和ap指向的两部分
void NewRoot (BTree *BT, BTree q, int x, BTree ap)
{
BTree p = (BTree)malloc(sizeof(BTNode));
if (!p)
exit (OVERFLOW);
p->keynum = 1;
p->parent = NULL;
p->key[1] = x;
p->ptr[0] = *BT;
p->ptr[1] = ap;
if (p->ptr[0])//原树不为空
p->ptr[0]->parent = p;
if (p->ptr[1]) //新分裂出的根结点
p->ptr[1]->parent = p;
*BT = p;
}
//生成含信息(BT,x,ap)的新的根节点*BT,原BT和ap为子树指针,q初始值为NULL
Status DeleteKey (BTree *BT, KeyType k)
{
Result R;
R = SearchBTree(*BT,k);
if (R.tag==1){
DeleteBTree(BT,R.pt,R.i);
return OK;
} else
return 0;
}
//从B树中删除关键字k
Status DeleteBTree (BTree *BT, BTree q, int i)
{ //找到真正需要删除的结点位置
Result R = {q,i,1};
if (i<1 || i>q->keynum)
return 0; //第i个关键词存在
if (q->ptr[i])
SearchMinKey(q->ptr[i],&R);
//右子树的最小结点替换当前结点
q->key[i] = R.pt->key[R.i];
Delete(BT,R.pt,R.i); //删除R.pt中第R.i个终端结点
return OK;
}
//从B树中删除结点q中的第i个关键字
void Delete (BTree *BT, BTree q, int i)
{
//删除R.pt中第R.i个终端结点,可能处于第一个,也可能处于中间某处
int s = ceil ((double)M/2);
BTree p = NULL;
int order = -1;
int tag = 0;
BTree lc,rc;
//寻找双亲结点,q为p的order孩子
if (!FoundParent(q,&p,&order))
tag = 1; //只有一个根结点
else {
if (q->keynum>=s)
tag = 2; //直接删除结点即可
else {
//需要删除整个结点,此时结点的关键字数目为s-1
if (tag==0 && order<p->keynum && p->ptr[order+1]->keynum>=s)
tag = 3; //右兄弟关键字个数大于最小值
if (tag==0 && order>0 && p->ptr[order-1]->keynum>=s)
tag = 4; //左兄弟关键字个数大于最小值
if (tag==0 && order<p->keynum && p->ptr[order+1]->keynum==s-1)
tag = 5; //右兄弟关键字个数等于最小值
if (tag==0 && order>0 &&p->ptr[order-1]->keynum==s-1)
tag = 6; //左兄弟关键字个数等于最小值
}
}
switch (tag){
case 1: //根节点是否只有一个需要删除的结点
if (q->keynum==1 && i==1){
*BT = q->ptr[0];
free (q);
} else {
//Status LeftMove (BTree old_ptr, int m, BTree new_ptr, int n, int len)
//从old-ptr的m序号开始拷贝到new-ptr的n序号,长度为len
LeftMove(q,i+1,q,i,q->keynum-i);
q->keynum--;
}
break;
case 2: //直接删除结点即可
LeftMove(q,i+1,q,i,q->keynum-i);
q->keynum--;
break;
case 3://右兄弟关键字个数大于最小值
rc = p->ptr[order+1]; //rc为右兄弟
LeftMove(q,i+1,q,i,q->keynum-i);
q->key[q->keynum] = p->key[order+1]; //紧靠上移的关键字下移到q中
q->ptr[q->keynum] = rc->ptr[0]; //将rc的结点挪到q的最后一位指针上
p->key[order+1] = rc->key[1]; //右兄弟最小关键字上升到双亲结点
rc->ptr[0] = rc->ptr[1]; //调整rc
LeftMove(rc,2,rc,1,rc->keynum-1);
rc->keynum--;
break;
case 4: //左兄弟关键字个数大于最小值
lc = p->ptr[order-1]; //lc为左兄弟
q->ptr[i] = q->ptr[i-1]; //删除结点的左边的指针向右挪动到删除结点的指针
RightMove(q,i-1,q,i,i-1); //key0 p0往后的单元向右挪动
q->key[1] = p->key[order]; //双亲结点值下移
q->ptr[0] = lc->ptr[lc->keynum]; //左兄弟最后一个指针挪动到q
p->key[order] = lc->key[lc->keynum]; //左兄弟最大值上移到p
lc->keynum--;
break;
case 5: //结点和右兄弟关键字个数均等于最小值
rc = p->ptr[order+1]; //结点的右兄弟
//Status LeftMove (BTree old_ptr, int m, BTree new_ptr, int n, int len)
//从old-ptr的m序号开始拷贝到new-ptr的n序号,长度为len
LeftMove(q,i+1,q,i,q->keynum-i); //删除目标值,
q->key[q->keynum] = p->key[order+1]; //双亲结点中的下一个值下移到q的最后一位
q->ptr[q->keynum] = rc->ptr[0];
LeftMove(rc,1,q,q->keynum+1,rc->keynum); //将右兄弟全部合并到q中
q->keynum += rc->keynum;
free (p->ptr[order+1]); //释放右兄弟
LeftMove(p,order+2,p,order+1,p->keynum-order-1); //删除p中下移结点
p->keynum--;
if (p->keynum<s-1){
//构造一个虚拟关键字
p->keynum++;
q = p;
Delete(BT,q,q->keynum);
}
break;
case 6: //结点和左兄弟关键字个数均等于最小值
lc = p->ptr[order-1]; //左兄弟结点
lc->key[lc->keynum+1] = p->key[order]; //双亲结点值下移到左兄弟结点
lc->ptr[lc->keynum+1] = q->ptr[0];
//Status LeftMove (BTree old_ptr, int m, BTree new_ptr, int n, int len)
//从old-ptr的m序号开始拷贝到new-ptr的n序号,长度为len
LeftMove(q,1,lc,lc->keynum+2,i-1);
LeftMove(q,i+1,lc,lc->keynum+i+1,q->keynum-i); //将q中删除结点的剩余结点全部拷贝到左兄弟中
lc->keynum += q->keynum;
free (p->ptr[order]); //释放q结点
LeftMove(p,order+1,p,order,p->keynum-order); //双亲结点中删除下移过后的order
p->keynum--;
if (p->keynum<s-1){
//如果双亲结点现有个数小于最小值则引起连锁反应
p->keynum++;
q = p;
Delete(BT,q,q->keynum); //删除不存在的结点
}
break;
}
}
//从B树中删除结点q中的第i个关键字
Status SearchMinKey (BTree BT, Result *R)
{
BTree q = BT;
while (q && q->ptr[0])
q = q->ptr[0]; //最下层左端点
if (q){
R->pt = q;
R->i = 1;
R->tag = 1;
return OK;
} else
return ERROR;
}
//找出BT中最小的关键字
Status FoundParent (BTree q, BTree *p, int *order)
{
*p = q->parent;
if (!(*p)){ //q为根节点
*order = -1;
return ERROR;
} else {
for (*order=0;(*p)->ptr[*order]!=q;(*order)++){}
return OK;
}
return OK;
}
//寻找双亲结点,q为p的order孩子
Status LeftMove (BTree old_ptr, int m, BTree new_ptr, int n, int len)
{
int k;
if (!old_ptr || !new_ptr || m<1 || m>old_ptr->keynum)
return ERROR;
for (k=0;k<len;k++,m++,n++){
new_ptr->key[n] = old_ptr->key[m];
new_ptr->ptr[n] = old_ptr->ptr[m];
}
return OK;
}
//向左移动关键字和指针
Status RightMove (BTree old_ptr, int m, BTree new_ptr, int n, int len)
{
int k;
if (!old_ptr || !new_ptr || m<1 || m>old_ptr->keynum)
return ERROR;
for (k=0;k<len;k++,m--,n--){
new_ptr->key[n] = old_ptr->key[m];
new_ptr->ptr[n-1] = old_ptr->ptr[m-1];
}
return OK;
}
//向右移动关键字和指针
void PrintBT_Level (BTree BT)
{
BTree p[MAX_LEVEL],q[MAX_LEVEL];
int i,j,k;
int a,b,count;
a = 1;
p[a] = BT;
count = 0;
while (a){
printf("第%2d行关键字:",++count);
b = 0;
for (i=1;i<=a;i++){
printf("(");
for (j=0;j<=p[i]->keynum;j++){
if (j)
printf(" %2d",p[i]->key[j]);
if (p[i]->ptr[j])
q[++b] = p[i]->ptr[j];
}
printf(") ");
}
printf("\n");
a = b;
for (k=1;k<=b;k++)
p[k] = q[k];
}
}
//层序输出B树
void PrintBT_InOrder (BTree BT)
{
int j;
if(BT){
for (j=0;j<=BT->keynum;j++){
PrintBT_InOrder(BT->ptr[j]);
if (j<BT->keynum)
printf("%d ",BT->key[j+1]);
}
}
}
//中序输出B树
#endif // B_TREE_C
#ifndef BASE_H
#define BASE_H
//查找表基础结构
#include <stdio.h>
#include <stdlib.h>
#include "Status.h"
#include "Scanf.h"
//查找表类型定义
typedef int KeyType;
typedef struct
{
KeyType key;
float weight; //其它域,用于拓展
} ElemType_Search; //有序表元素类型
//0号单元弃用
typedef struct
{
ElemType_Search *elem; //数据元素存储空间基址
int length;
}Table;
//函数列表
Status Create (FILE *fp, Table *T, int n);
void Destory (Table *T);
void Traverse (Table T,void(*Visit)(ElemType_Search));
void PrintKey (ElemType_Search e);
//只输出key域
#endif // BASE_H
#ifndef BASE_C
#define BASE_C
#include "Base.h"
////查找表类型定义
//typedef int KeyTpe;
//typedef struct
//{
// KeyTpe key;
// float weight; //其它域,用于拓展
//} ElemType_Search; //有序表元素类型
//
////0号单元弃用
//typedef struct
//{
// ElemType_Search *elem; //数据元素存储空间基址
// int length;
//}Table;
//函数列表
Status Create (FILE *fp, Table *T, int n)
{
T->elem = (ElemType_Search *)malloc ((n+1)*sizeof(ElemType_Search));
if (!T->elem)
exit (OVERFLOW);
//0号单元弃用
int i;
int a;
float b;
for (T->length=0,i=1;i<=n;i++){
if (Scanf(fp,"%d%f",&a,&b)==2){
T->elem[i].key = a;
T->elem[i].weight = b;
T->length++;
}
}
return OK;
}
void Destory (Table *T)
{
if (T->elem)
free (T->elem);
T->elem = NULL;
T->length = 0;
}
void Traverse (Table T,void(*Visit)(ElemType_Search))
{
int i;
for (i=0;i<T.length;i++){
if (i && !(i%10))
printf("\n"); //每10个元素打一个回车
Visit (T.elem[i+1]);
}
printf("\n");
}
void PrintKey (ElemType_Search e)
{
printf("%d ",e.key);
}
//只输出key域
#endif // BASE_C
#ifndef SCANF_H
#define SCANF_H
#include <stdio.h>
#include <string.h>
#include <stdarg.h> //提供宏va_list、va_start、va_arg、va_end
#include <ctype.h> //提供isprint原型
/*
自定义的数据录入函数,用于从文件fp
中读取格式化的输入。
与fscanf不同之处在于此函数只会读取
西文字符,对于中文字符,则会跳过。
*/
int Scanf(FILE *fp, char *format, ...);
#endif // SCANF_H
/*********************
* *
* 文件夹: ▲01 绪论 *
* *
* 文件名: Scanf.c *
* *
*********************/
#ifndef SCANF_C
#define SCANF_C
#include "Scanf.h"
/*
自定义的数据录入函数,用于从文件fp
中读取格式化的输入。
与fscanf不同之处在于此函数只会读取
西文字符,对于中文字符,则会跳过。
*/
int Scanf(FILE *fp, char *format, ...)
{
int *i;
char *ch, *s;
float *f;
int count, k, len, n;
int tmp;
va_list ap;
len = strlen(format);
va_start(ap, format);
for(count=0,k=2; k<=len; k=k+2)
{
while((tmp=getc(fp))!=EOF) //跳过所有非西文字符
{
if((tmp>=0 && tmp<=127))
{
ungetc(tmp, fp); //遇到首个西文字符,将此西文字符放入输入流
break;
}
}
if(tmp==EOF)
break;
if(format[k-1]=='c') //读取字符
{
ch = va_arg(ap, char*);
if(tmp!=EOF)
count += fscanf(fp, "%c", ch);
}
if(format[k-1]=='d') //读取整型
{
i = va_arg(ap, int*);
while((tmp=getc(fp))!=EOF)
{
if((tmp>='0' && tmp<='9') || tmp=='-' || tmp=='+')
{
ungetc(tmp, fp);
break;
}
}
if(tmp!=EOF)
count += fscanf(fp, "%d", i);
}
if(format[k-1]=='f') //读取浮点型
{
f = va_arg(ap, float*);
while((tmp=getc(fp))!=EOF)
{
if((tmp>='0' && tmp<='9') || tmp=='-' || tmp=='+'|| tmp=='.' )
{
ungetc(tmp, fp);
break;
}
}
if(tmp!=EOF)
count += fscanf(fp, "%f", f);
}
if(format[k-1]=='s') //读取字符串
{
s = va_arg(ap, char*);
while((tmp=getc(fp))!=EOF && (!isprint(tmp) || tmp==' '))
;
n = 0;
if(!feof(fp))
{
ungetc(tmp, fp);
while((tmp=getc(fp))!=EOF)
{
if(isprint(tmp) && tmp!=' ')
s[n++] = tmp;
else
break;
}
ungetc(tmp, fp);
}
s[n] = '\0';
count++;
}
}
va_end(ap);
return count;
}
#endif
/*********************
* *
* 文件夹: ▲01 绪论 *
* *
* 文件名: Scanf.c *
* *
*********************/
#ifndef SCANF_C
#define SCANF_C
#include "Scanf.h"
/*
自定义的数据录入函数,用于从文件fp
中读取格式化的输入。
与fscanf不同之处在于此函数只会读取
西文字符,对于中文字符,则会跳过。
*/
int Scanf(FILE *fp, char *format, ...)
{
int *i;
char *ch, *s;
float *f;
int count, k, len, n;
int tmp;
va_list ap;
len = strlen(format);
va_start(ap, format);
for(count=0,k=2; k<=len; k=k+2)
{
while((tmp=getc(fp))!=EOF) //跳过所有非西文字符
{
if((tmp>=0 && tmp<=127))
{
ungetc(tmp, fp); //遇到首个西文字符,将此西文字符放入输入流
break;
}
}
if(tmp==EOF)
break;
if(format[k-1]=='c') //读取字符
{
ch = va_arg(ap, char*);
if(tmp!=EOF)
count += fscanf(fp, "%c", ch);
}
if(format[k-1]=='d') //读取整型
{
i = va_arg(ap, int*);
while((tmp=getc(fp))!=EOF)
{
if((tmp>='0' && tmp<='9') || tmp=='-' || tmp=='+')
{
ungetc(tmp, fp);
break;
}
}
if(tmp!=EOF)
count += fscanf(fp, "%d", i);
}
if(format[k-1]=='f') //读取浮点型
{
f = va_arg(ap, float*);
while((tmp=getc(fp))!=EOF)
{
if((tmp>='0' && tmp<='9') || tmp=='-' || tmp=='+'|| tmp=='.' )
{
ungetc(tmp, fp);
break;
}
}
if(tmp!=EOF)
count += fscanf(fp, "%f", f);
}
if(format[k-1]=='s') //读取字符串
{
s = va_arg(ap, char*);
while((tmp=getc(fp))!=EOF && (!isprint(tmp) || tmp==' '))
;
n = 0;
if(!feof(fp))
{
ungetc(tmp, fp);
while((tmp=getc(fp))!=EOF)
{
if(isprint(tmp) && tmp!=' ')
s[n++] = tmp;
else
break;
}
ungetc(tmp, fp);
}
s[n] = '\0';
count++;
}
}
va_end(ap);
return count;
}
#endif
#include <stdio.h>
#include <stdlib.h>
#include "B-Tree.h"
#define MAX 15
int main()
{
Table T;
BTree BT;
FILE *fp = fopen("TestDataTable.txt","r");
if (!fp)
exit (-1);
Create(fp,&T,MAX);
// Traverse(T,Print);
// printf("\n");
Traverse(T,PrintKey);
printf("\n");
// Destory(&T);
// printf("length = %d\n",T.length);
printf("构造B树并输出关键字\n");
CreateBTree(&BT,T);
PrintBT_InOrder(BT);
printf("\n");
printf("层序输出\n");
PrintBT_Level(BT);
printf("\n\n");
int k = 45;
printf("删除关键字%d,\n",k);
DeleteKey(&BT,k);
printf("中序输出关键字\n");
PrintBT_InOrder(BT);
printf("\n");
printf("层序输出\n");
PrintBT_Level(BT);
printf("\n\n");
return 0;
}
TestData输入样例 :(24,0.0) (45,0.0) (50,0.0) (53,0.0) (100,0.0) (37,0.0)
(12,0.0) (61,0.0) (90,0.0) (70,0.0) (3,0.0) (30,0.0)
(26,0.0) (85,0.0) (7,0.0)