前天和昨天一直在写实验六的题目二,想挑战一下自己,所以选择用B-树的数据结构,这篇是一篇关于B-树的总结。
B-树是一种平衡的多路查找树。
一棵m阶的B-树,或者为空树,或为满足下列特征的m叉树:
-
树中每个结点至多有m棵子树;
-
若根结点不是叶子结点,则至少有两棵子树;
-
除根之外的所有非终端结点至少有m/2(向上取)棵子树;
-
所有的非终端结点中包含下列信息数据
(n,A0,K1,A1,K2,A2,…,Kn,An)、
其中:Ki(i=1,…,n)为关键字,且Ki<K(i+1)(i=1,…,n-1);Ai(i=0,…,n)为指向子树根结点的指针,且指针A(i-1)所指的子树中所有结点的关键字均小于Ki(i=1,…,n),An所指子树中所有结点的关键字均大于Kn,n(m/2(向上取)-1<=n<=m-1)为关键字的个数(或n+1为子树个数)。 -
所有的叶子结点都出现在同一层次上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)
B-树的结构体
#define m 3
typedef struct BTNode //B-树的阶,暂设为3
{
int keynum; //结点中关键字个数,即结点的大小
struct BTNode *parent; //指向双亲结点
KeyType key[m+1]; //关键字向量,0号单元未用
struct BTNode *ptr[m+1]; //子树指针向量
Record *recptr[m+1]; //记录指针向量,0号单元未用
}BTNode,*BTree; //B-树结点和B-树的类型
typedef struct
{
BTNode *pt; //指向找到的结点
int i; //1,....,m,在结点中的关键字序号
int tag; //1:查找成功,0:查找失败
}Result; //B-树的查找结果类型
具体实现算法
Status InitBTree(BTree &t) //生成一个新B-树
{
t = NULL;
return OK;
}
int SearchBTNode(BTNode *p, KeyType k) //在结点中寻找关键字的位置
{
int i = 0;
while (i < p->keynum&&strcmp(p->key[i + 1], k) <= 0)
{
i++;
}
return i;
}
Result SearchBTree(BTree t, KeyType k)
{
BTNode *p = t, *q = NULL; //初始化结点p和结点q,p指向待查结点,q指向p的双亲
int found_tag = 0; //设定查找成功与否标志
int i = 0;
Result r; //设定返回的查找结果
while (p != NULL && found_tag == 0)
{
i = SearchBTNode(p, k); //在结点p中查找关键字k
if (i > 0 && strcmp(p->key[i], k) == 0) //查找成功
{
found_tag = 1;
}
else //查找失败
{
q = p;
p = p->ptr[i];
}
}
if (found_tag == 1) //查找成功
{
r.pt = p;
r.i = i;
r.tag = 1;
}
else //查找失败
{
r.pt = q;
r.i = i;
r.tag = 0;
}
return r; //返回关键字k的位置(或者插入位置)
}
void InsertBTNode(BTNode *&p, int i, KeyType k, BTNode *q,int a) //将p->key[s+1,...,m],p->ptr[s+1,...,m]和p->recptr[s+1,...,m]移入新节点q
{
int j;
for (j = p->keynum;j > i;j--) //整体后移空出一个位置
{
strcpy(p->key[j + 1], p->key[j]);
p->recptr[j + 1] = p->recptr[j];
p->ptr[j + 1] = p->ptr[j];
}
strcpy(p->key[i + 1], k);
p->recptr[i + 1] = a;
p->ptr[i + 1] = q;
if (q != NULL)
q->parent = p;
p->keynum++;
}
void SplitBTNode(BTNode*&p, BTNode *&q) //将p->key[s+1,...,m],p->ptr[s+1,...,m]和p->recptr[s+1,...,m]移入新节点q
{
int i;
int s = (m + 1) / 2;
q = (BTNode *)malloc(sizeof(BTNode)); //给结点q分配空间
q->ptr[0] = p->ptr[s];
for (i = s + 1;i <= m;i++) //后一半移入结点q
{
strcpy(q->key[i - s], p->key[i]);
q->recptr[i - s] = p->recptr[i];
q->ptr[i - s] = p->ptr[i];
}
q->keynum = p->keynum - s;
q->parent = p->parent;
for (i = 0;i <= p->keynum - s;i++) //修改双亲指针
{
if (q->ptr[i] != NULL)
q->ptr[i]->parent = q;
}
p->keynum = s - 1; //结点p的前一半保留,修改结点p的keynum
}
void NewRoot(BTNode *&t, KeyType k, BTNode *p, BTNode *q,int a)
{
t = (BTNode *)malloc(sizeof(BTNode)); //分配空间
t->keynum = 1;
t->ptr[0] = p;
t->ptr[1] = q;
strcpy(t->key[1], k);
t->recptr[1] = a;
if (p != NULL) //调整结点p和q的双亲指针
p->parent = t;
if (q != NULL)
q->parent = t;
t->parent = NULL;
}
void InsertBTree(BTree &t, int i, KeyType k, BTNode *p,int a)
{
BTNode *q;
int b;
int finish_tag, newroot_tag, s; //设定需要新结点标志和插入完成标志
KeyType x;
if (p == NULL) //t是空树
NewRoot(t, k, NULL, NULL,a); //生成仅含关键字k的根结点t
else
{
strcpy(x,k);
b = a;
q = NULL;
finish_tag = 0;
newroot_tag = 0;
while (finish_tag == 0 && newroot_tag == 0)
{
InsertBTNode(p, i, x, q,b); //将关键字x和结点q分别插入到p->key[i+1]和p->[i+1]
if (p->keynum <= Max) //插入完成
finish_tag = 1;
else
{
s = (m + 1) / 2;
SplitBTNode(p, q); //分裂结点
strcpy(x, p->key[s]);
b = p->recptr[s];
if (p->parent) //查找x 的插入位置
{
p = p->parent;
i = SearchBTNode(p, x);
}
else //没找到x,需要新结点
newroot_tag = 1;
}
}
if (newroot_tag == 1) //根节点已分裂为p和q
NewRoot(t, x, p, q,b); //生成新的根结点,p和q都是子树指针
}
}
这里的算法,只要认真理解一下,其实不是很难,我自己的一个难点是如何实现对记录进行处理,即对recptr的处理。这个关键就是在进行关键字的插入,移动时,记得一定要同时移动或插入相应的记录。
在这次作业中,我还遇到了几个小问题,这里顺便记一下。
- 比较两个字符串s1和s2,不能用s1= =s2,因为这样只是比较第一个字符,而用函数strcmp()才是两个字符串的比较,如果s1<s2,则strcmp的返回值<0,s1= =s2,则返回值= =0,若s1>s2,则返回值>0。
- 字符串的定义,用strcpy()函数。
- 自定义结构体中,不能使用常量数据类型。
下面是一些参考资料:
https://blog.youkuaiyun.com/geek_jerome/article/details/78895289
https://blog.youkuaiyun.com/geek_jerome/article/details/78895289
最后附上完整代码:
#include "pch.h"
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <string.h>
typedef int Status;
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFESIBLE -1
#define OVERFLOW -2
// 表示函数的状态
#define MAXM 11 //定义B树的最大的阶数
const int m = 4; //设定B树的阶数
const int Max = m - 1; //结点的最大关键字数量
const int Min = (m - 1) / 2; //结点的最小关键字数量
typedef char KeyType[15]; //KeyType为关键字类型
typedef struct node //B树和B树结点类型
{
int keynum; //结点关键字个数
KeyType key[MAXM]; //关键字数组,key[0]不使用
struct node *parent; //双亲结点指针
struct node *ptr[MAXM]; //孩子结点指针数组
int recptr[MAXM]; //记录指针向量,0号单元未用
}BTNode, *BTree;
typedef struct //B树查找结果类型
{
BTNode *pt; //指向找到的结点
int i; //在结点中的关键位置
int tag; //查找成功与否标志
}Result;
Status InitBTree(BTree &t)
{
t = NULL;
return OK;
}
int SearchBTNode(BTNode *p, KeyType k)
{
int i = 0;
while (i < p->keynum&&strcmp(p->key[i + 1], k) <= 0)
{
i++;
}
return i;
}
Result SearchBTree(BTree t, KeyType k)
{
BTNode *p = t, *q = NULL; //初始化结点p和结点q,p指向待查结点,q指向p的双亲
int found_tag = 0; //设定查找成功与否标志
int i = 0;
Result r; //设定返回的查找结果
while (p != NULL && found_tag == 0)
{
i = SearchBTNode(p, k); //在结点p中查找关键字k
if (i > 0 && strcmp(p->key[i], k) == 0) //查找成功
{
found_tag = 1;
}
else //查找失败
{
q = p;
p = p->ptr[i];
}
}
if (found_tag == 1) //查找成功
{
r.pt = p;
r.i = i;
r.tag = 1;
}
else //查找失败
{
r.pt = q;
r.i = i;
r.tag = 0;
}
return r; //返回关键字k的位置(或者插入位置)
}
void InsertBTNode(BTNode *&p, int i, KeyType k, BTNode *q,int a) //在m阶B-树上结点*q的key[i]和key[i+1]之间插入关键字k
{
int j;
for (j = p->keynum;j > i;j--) //整体后移空出一个位置
{
strcpy(p->key[j + 1], p->key[j]);
p->recptr[j + 1] = p->recptr[j];
p->ptr[j + 1] = p->ptr[j];
}
strcpy(p->key[i + 1], k);
p->recptr[i + 1] = a;
p->ptr[i + 1] = q;
if (q != NULL)
q->parent = p;
p->keynum++;
}
void SplitBTNode(BTNode*&p, BTNode *&q) //将p->key[s+1,...,m],p->ptr[s+1,...,m]和p->recptr[s+1,...,m]移入新节点q
{
int i;
int s = (m + 1) / 2;
q = (BTNode *)malloc(sizeof(BTNode)); //给结点q分配空间
q->ptr[0] = p->ptr[s];
for (i = s + 1;i <= m;i++) //后一半移入结点q
{
strcpy(q->key[i - s], p->key[i]);
q->recptr[i - s] = p->recptr[i];
q->ptr[i - s] = p->ptr[i];
}
q->keynum = p->keynum - s;
q->parent = p->parent;
for (i = 0;i <= p->keynum - s;i++) //修改双亲指针
{
if (q->ptr[i] != NULL)
q->ptr[i]->parent = q;
}
p->keynum = s - 1; //结点p的前一半保留,修改结点p的keynum
}
void NewRoot(BTNode *&t, KeyType k, BTNode *p, BTNode *q,int a) //s生成含信息(p,q,k)的新的根节点*t,q和p为子树指针
{
t = (BTNode *)malloc(sizeof(BTNode)); //分配空间
t->keynum = 1;
t->ptr[0] = p;
t->ptr[1] = q;
strcpy(t->key[1], k);
t->recptr[1] = a;
if (p != NULL) //调整结点p和q的双亲指针
p->parent = t;
if (q != NULL)
q->parent = t;
t->parent = NULL;
}
void InsertBTree(BTree &t, int i, KeyType k, BTNode *p,int a)
{
BTNode *q;
int b;
int finish_tag, newroot_tag, s; //设定需要新结点标志和插入完成标志
KeyType x;
if (p == NULL) //t是空树
NewRoot(t, k, NULL, NULL,a); //生成仅含关键字k的根结点t
else
{
strcpy(x,k);
b = a;
q = NULL;
finish_tag = 0;
newroot_tag = 0;
while (finish_tag == 0 && newroot_tag == 0)
{
InsertBTNode(p, i, x, q,b); //将关键字x和结点q分别插入到p->key[i+1]和p->[i+1]
if (p->keynum <= Max) //插入完成
finish_tag = 1;
else
{
s = (m + 1) / 2;
SplitBTNode(p, q); //分裂结点
strcpy(x, p->key[s]);
b = p->recptr[s];
if (p->parent) //查找x 的插入位置
{
p = p->parent;
i = SearchBTNode(p, x);
}
else //没找到x,需要新结点
newroot_tag = 1;
}
}
if (newroot_tag == 1) //根节点已分裂为p和q
NewRoot(t, x, p, q,b); //生成新的根结点,p和q都是子树指针
}
}
int main()
{
BTNode *t = NULL;
Result s,i;
int j, n = 10;
KeyType k;
KeyType a[10], str = {0};
char ch[15];
int b[10];
srand((unsigned int)time(0));
for (int k = 0;k < 10;k++)
{
b[k] = (rand()<<7)+rand(); //左移7位生成随机数
for (int j = 0; j < 4; j++) { str[j] = rand() % 26 + 'A'; } //随机生成字符串
strcpy(a[k], str);
}
for (j = 0;j < n;j++)
{
s = SearchBTree(t, a[j]);
if (s.tag == 0)
InsertBTree(t, s.i, a[j], s.pt,b[j]);
}
puts("电话查询系统:");
puts("------------------------------------------------------------------");
for (int k = 0;k < 10;k++)
{
printf("%s:%d\t", a[k], b[k]);
if (0 == k % 5&&k!=0)
printf("\n");
}
printf("\n");
puts("------------------------------------------------------------------");
while (*ch != '0')
{
puts("请输入你想要找的人的姓名(区别大小写),输入0表示结束");
scanf("%s", ch);
i = SearchBTree(t, ch);
if (i.tag == 0)
puts("没有找到这个人");
else
printf("这个人的电话号码是:%d\n", i.pt->recptr[i.i]);
}
}