广工数据结构实验设计报告BTree
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
数据结构设计性实验报告
课程名称数据结构实验
题目名称B树(难度1.4)
学生学院计算机学院
专业班级
学号
姓名
指导教师黄剑锋
2015年 06月25日
B树抽象数据类型实现
一、设计简介
本次设计在AnyviewCL自由编程平台上实现了B树的6种基本操作,并根据这个基本操作设计了友好的交际界面,操作简单易懂,并在AnyviewCL自由编程平台上可视化测试B树各个基本操作,保证了各基本的操作算法的正确性。
经在AnyviewCL自由编程平台严格测试后,将本设计移植到Visual C++ 6.0平台生成可运行程序,并进行各个基本操作的测试,保证了程序运行的稳定性。
其中数据来源为一组在0~1000内的int型随机数,但数据由typedefintKeyType定义,若需要改变数据类型,只需要将int替换成所需的数据类型即可。
二、抽象数据类型定义及各种基本操作的描述
ADT BTree{
数据对象: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)所有的叶子节点都在同一层,子叶结点不包含任何信息。
基本操作P:
CreatBTree(&T, n, m);
初始条件:初始化关键字个数n大于等于0,B树的阶数m大于3小于等于20。
操作结果:构建一棵阶数为m,含有n个关键字的B树。
SearchBTree(T, k, &p);
初始条件:树T存在。
操作结果:在m阶B树t上查找关键字k,返回(pt,i,tag) 。
InsertBTree(&T, k, p->pt, p->i, m);
初始条件:树T存在,p->pt指向T中某个结点
操作结果:在B树T上结点p->pt的key[i]和key[i+1]之间插入关键字k DeleteBTree(p->pt, p->i, m, T);
初始条件:树T存在,p->pt指向T中某个结点
操作结果:删除B树T上结点p->pt的关键字k
PrintBTree(T);
初始条件:树T存在
操作结果:中序遍历B树
DestroyBTree(T)
初始条件:树T存在
操作结果:销毁B树
}ADTBTree
三、存储结构定义
#include<stdio.h>
#include<stdlib.h>
#include <time.h>
#define TRUE 1
#define FALSE 0
#define OVERFLOW -2
#define OK 1
#define ERROR 0
typedefintKeyType;
typedefint Status;
typedefstruct
{
KeyType key;
char data;
}Record;
typedefstructBTNode
{
intkeynum; // 结点中关键字个数,即结点的大小structBTNode *parent; // 指向双亲结点
KeyType key[21]; // 关键字向量,0号单元未用structBTNode *ptr[21]; // 子树指针向量
Record *recptr[21]; // 记录指针向量,0号单元未用}BTNode, *BTree; // B树结点和B树的类型
typedefstruct
{
BTNode *pt; // 指向找到的结点
int i; // 1..m,在结点中的关键字序号
int tag; // 1:查找成功,0:查找失败
}restype,*result; // 在B树的查找结果类型
四、关键算法设计流程图
主函数:MAIN();
功能:确定B树阶数与初始结点数,提供基本的菜单功能选择
开始
结束
输入阶数与初始结点数初始化B 树查找关键字插入关键字删除关键字打印B 树销毁B 树CreatBTree()SearchBTree()SearchBTree()
InsertBTree()SearchBTree()DeleteBTree()PrintBTree()DestroyBTree()
退出程序
否
是
函数名:intSearchNode(BTree p, int k);
功能:在节点中查找关键字,返回该关键字在节点中的位置。
开始
i = 1
i <= p -> keynum
返回 i
k > p -> key[i]
i++
结束
是
是否否
函数名:voidSearchBTree(BTree t, int k, result &SearchBTree);
功能:在m 阶B 树t 上查找关键码k ,返回(pt,i,tag)。
开始
p != NULL && 0 == found
结束是否
i = 0 , found = 0 , p = t , q = NULL
调用SearchNode()函数,查找对
于该插入结点p 的插入位置i
找到关键字
found = 1
q = p , p = p -> ptr[i-1]
查找成功
查找结果类型指针指向待
查结点,特征值tag = 1
查找结果类型指针指向应插入结点位置,特征值tag = 0
否是是否
函数名:void split(BTree&q, int s, BTree&ap);
功能:将结点q 分裂成两个结点,前一半保留,后一半移入新生结点ap 。
开始
结束
n = q -> keynum ap ->ptr[0] = q -> ptr[s]
将q 结点中后一半(序号大于s)的
关键字和指针插入到新建结点ap
ap -> kaynum = n – s ap -> parent = q -> parent q -> keynum = s -1
将ap 中子树指针父结点指向ap
函数名:voidnewroot(BTree&t, BTree p, int x, BTreeap);
功能:生成新的根结点。
开始p != NULL
结束否
给t 分配空间与赋值
p -> parent = t
ap != NULL
t -> parent = NULL
ap -> parent = t
是是否
函数名:void Insert(BTree&q, int i, int x, BTreeap);
功能:将关键字ap 分别插入到q->key[i+1]和q->ptr[i+1]中。
开始
j >= i
结束
否
j = q -> keynum
q -> key[j]后移
q -> ptr[j]后移
ap != NULL
插入赋值
ap -> parent = q
是j = j - 1
关键字数加 1
否是
函数名:voidInsertBTree(BTree&t, int k, BTree q, int i, int m);
功能:在B 树t 上结点*q 的key[i]和key[i+1]之间插入关键字k 。
开始
finshed = 0 && neednewroot = 0
结束finished = 0 , neednewroot = 0q = NULL
调用split()函数分裂结点q 否是调用newroot()函数生成新的根结点
否是调用Insert()函数插入关键字
q -> keynum < m
finished = 1
t 是空树或者根结点
已分裂为结点q 和ap
是否否是
函数名:void Successor(BTree&p, int i);
功能:由后继最下层非终端结点的最小关键字代替结点中关键字key[i]。
开始
u -> ptr[1]不为空
结束
u = p -> ptr[i+1]
p -> key[i] = u -> key[1]
u = u -> ptr[1]
p = u
否是
函数名:void Remove(BTree&p, int i);
功能:从结点p 中删除key[i]。
开始
j < p -> keynum
结束
j = i
p -> key[j]左移
p -> ptr[j]左移
p -> ptr[p -> keynum] = NULL
j = j + 1
关键字数减 1
否是
函数名:void Restore(BTree&p, int i, int m, BTree&T); 功能:调整B 树。
开始
结束
finished = 0 , ap = p -> parent
finished == 0
否
是
否
调整后出现空的根结点
是
否
否
是
向左兄弟借关键字
处理左子树lc 和右子树rc
向右兄弟借关键字
重新确定p 在ap 子树的位置
与左兄弟合并
根结点不为空且结点数大于(m-1)/2
父结点的关键字与p 合并将结点p 中关键字和指针移到p 左兄弟中释放p 结点空间
父结点的关键字与p 合并
将结点p 中关键字和指针移到p 右兄弟中释放p 结点空间
结点关键字右移
父亲插入到结点修改p 中的子女的父结点为p 左兄弟上移到父亲位置
父亲插入到结点修改p 中的子女的父结点为p 右兄弟上移到父亲位置
结点关键字左移
确定p 在ap 子树的位置是
否
否
是
函数名:voidDeleteBTree(BTree p, int i, int m, BTree&T); 功能:删除B 树T 上结点p 的关键字k 。
开始
p -> ptr[i-1] != NULL
(不是最下层非终端结点)
结束
调用Successor()函数
删除后关键字个数小于(m-1)/2
调用Remove()
函数删除关键字
调用Restore()函数调整B 树
否
是
调用DeleteBTree()函数
否
是
五、 程序测试
1、AnyviewCL 自由编程平台上测试结果 1)3阶B 树的测试:
此时B 树的结构为:
进行查找操作:
进行插入操作:
插入后B树的结构为:
进行删除操作(直接删除关键字):
删除关键字618后B树的结构为:
此时对B树进行打印操作:
继续进行删除操作:(删后结点与左兄弟合并)
删除关键字798后B树的结构为:
继续进行删除操作:(直接删除关键字)
删除关键字796后B树的结构为:
继续进行删除操作:(删后结点与右兄弟合并、父节点与左兄弟合并)
删除关键字580后B树的结构为:
继续进行删除操作:(删后结点与左兄弟合并、父节点与右兄弟合并,树的高度减1)
删除关键字281后B树的结构为:
进行B树的销毁:
此时AnyviewCL的演示区情况为:
2)5阶B树的测试:
初始化B树的结构后,B树的结构为:
进行插入操作:
插入关键字580后B树的结构为:
进行删除操作:(删后结点与右兄弟合并)
删除关键字21后B树的结构为:
继续进行删除操作:(寻找后继结点并与之交换,删后向左兄弟借关键字)
删除关键字596后B树的结构为:
进行打印B树操作:
2、在Visual C++ 6.0平台测试:
B树设置界面:
进行B树初始话操作:
进行打印B树操作:
进行查找操作:
进行插入操作:
插入关键字588后的B树结构为:
进行删除操作:
删除关键字532后的B树结构为:
进行销毁B树操作:
销毁后B树为空,无法打印:
退出程序操作:
六、思考与小结
1、B树的高度:
若N>=1,则对任意一棵包含N个关键字、高度为h、阶数为m的B树:
1)因为B树中每个结点最多有m棵子树,m-1个关键字,所以在一棵高度为h的m阶B 树中的关键字个数应满足:N<=m^h-1,因此有h>=logm(N+1);
2)若让每个结点中的关键字个数达到最少,则容纳同样多关键字的B树的高度可达到最大,此时有h<=log┌m/2┐((N+1)/2+1)。
2、复杂度:
每次查找共需访问O(logmN)个结点,由此可知,对存有N个关键字的m阶B树的每次查找,耗时不超过O(logmN)。
七、总结和体会
本次设计主要在AnyviewCL自由编程平台上进行编程,通过深入学习课本提供的查找与插入算法后,我很轻易地写出了构建的算法,再根据递归的知识完成了B树的中序遍历算法和销毁算法。
本次设计的难点在于B树的调整算法,B 树在插入的过程中可能发生上溢,此时需要通过分裂结点来处理,在此过程中B 树可能出现长高。
而在删除的过程中则可能发生下溢,此时需要分:借左兄弟、借右兄弟、与左兄弟合并、与右兄弟这四种调整方式来处理,并要处理合并后根结点为空的情况,在此过程中B树可能出现高度降低。
在该平台的可视化调试功能的帮助严格测试了下各个基本操作的算法设计,改进算法中一些可能出现的错误,保证了代码的正确性。
然后将代码移植到Visual C++ 6.0中,编译并运行后,初始化B树时程序出现异常代码:c0000005,在Dubug模式下找到原因是操作了未经初始化的对象,而代码本身在AnyviewCL 自由编程平台上是可以顺利运行的,后来经过对几个关键指针的初始化Bug顺利排除。
究其原因可能是AnyviewCL平台在实现可视化的过程已经对所有运用到的指针都进行了初始化。
总而言之,在这次设计中我深入理解了B树的各项基本操作,也很好地锻炼了自己的动手能力,收获匪浅。
八、程序关键源码
//BTree for Visual C++ 6.0
//By:2013级计算机科学与技术3班3113005867方典禹
//包含文件BTree.cpp 、Operation.h、BTree.cpp
Datastructure.h
//*******************定义数据类型***********************//
BTree.cpp
//*******************主函数***********************//
Operation.h
#include "Datastructure.h"
//******************************基本操作函数****************************// intSearchNode(BTree p, int k)
//在p->key[1..keynum]找p->key[i]<=k<p->key[i+1],并返回i
{
int i = 1;
while(i <= p->keynum&& k > p->key[i]) i++;
return i;
}
voidSearchBTree(BTree t, int k, result &SearchBTree)
// 在m阶B树t上查找关键码k,返回(pt,i,tag)。
// 若查找成功,则特征值tag=1,指针pt所指结点中第i个关键码等于k;否则,
// 特征值tag=0,等于k的关键码记录应插入在指针pt所指结点中第i个和第i+1个关键码间{
int i = 0, found = 0;
BTree p = t, q = NULL; // 初始,p指向待查结点,q指向p的双亲
while(p != NULL && 0 == found)
{
i = SearchNode(p, k); // 在p->key[1..keynum]找p->key[i]<=k<p->key[i+1]
if(i>0 && p->key[i] == k)
found = 1; // 找到待查关键字
else
{
q = p;
p = p->ptr[i-1];
}
}
if(1 == found) // 查找成功
{
SearchBTree ->pt = p;
SearchBTree -> i = i;
SearchBTree -> tag = 1;
}
else // 查找不成功,返回key的插入位置i
{
SearchBTree ->pt = q;
SearchBTree -> i = i;
SearchBTree -> tag = 0;
}
}
void split(BTree&q, int s, BTree&ap)
// 将结点q分裂成两个结点,前一半保留,后一半移入新生结点ap {
int i, j, n = q ->keynum;
ap = (BTNode*)malloc(sizeof(BTNode)); // 生成新结点ap
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])
ap ->ptr[i] -> parent = ap;
q ->keynum = s - 1; // q的前一半保留,修改keynum
}
voidnewroot(BTree&t, BTree p, int x, BTreeap)
// 生成新的根结点
{
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, BTreeap)
{
intj,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++;
}
voidInsertBTree(BTree&t, int k, BTree q, int i, int m)
// 在B树t上结点*q的key[i]和key[i+1]之间插入关键字k
// 若引起结点过大,则沿双亲链进行必要的结点分裂调整,使T仍是m阶的B树{
int x, s;
BTreeap;
int finished = 0, neednewroot = 0;
if(NULL==q)
newroot(t, NULL, k, NULL);
else
{
x = k; ap = NULL;
while(0 == neednewroot&& 0 == finished)
{
Insert(q, i, x, ap); // key和ap分别插到q->key[i+1]和q->ptr[i+1]
if(q->keynum< m)
finished = 1; // 插入完成
else // 分裂结点q
{
s = (m+1)/2;
split(q, s, ap);
x = q -> key[s];
if(q -> parent)
{
q = q -> parent;
i = SearchNode(q, x); // 在双亲结点*q中查找x的插入位置
}
elseneednewroot = 1;
}
}
if(1 == neednewroot) // t是空树或者根结点已分裂为结点q和ap
newroot(t, q, x, ap); // 生成含信息(t,x,ap)的新的根结点root
}
}
void Successor(BTree&p, int i)
//由后继最下层非终端结点的最小关键字代替结点中关键字key[i]
{
BTNode *u;
while(NULL != u ->ptr[0]) //找出关键字的后继{
u = u ->ptr[0];
}
p -> key[i] = u -> key[1];
p = u;
}
void Remove(BTree&p, int i)
//从结点p中删除key[i]
{
intj,n = p ->keynum;
for(j = i; j < n; j++ ) //关键字左移
{
p -> key[j] = p -> key[j+1];
p ->ptr[j] = p ->ptr[j+1];
}
//free(p ->ptr[n]);
//p ->ptr[n] = NULL;
p ->keynum -- ;
}
void Restore(BTree&p, int i, int m, BTree&T)
//调整B树
{
int j;
BTreeap = p -> parent;
BTreelc, rc, pr;
int finished = 0, r = 0;
while(0 == finished)
{
r = 0;
while(ap ->ptr[r] != p) //确定p在ap子树的位置r++;
if(r == 0)
{
r++;
lc = NULL;
rc = ap ->ptr[r];
}
else if(r == ap ->keynum)
{
rc = NULL;
}
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)) //与左兄弟合并
{
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+1] = 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++)
p ->ptr[p ->keynum + j] -> parent = lc;
}
lc ->keynum = lc ->keynum + p ->keynum; //合并后关键字的个数
ap ->keynum--;
pr = p;
free(pr); //释放p结点空间
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];
if(p ->ptr[0]) //修改p中的子女的父结点为rc
{
for(j = 1; j <= 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); //释放p结点空间
pr = NULL;
p = rc;
}
ap = ap -> parent;
if(p -> parent ->keynum>= (m-1)/2 || (NULL == ap&& p -> parent ->keynum>0))
finished = 1;
else if(NULL == ap) //若调整后出现空的根结点,则删除该根结点,树高减1
{
pr = T;
T = p; //根结点下移
free(pr);
pr = NULL;
finished = 1;
}
p = p -> parent;
}
}
voidDeleteBTree(BTree p, int i, int m, BTree&T)
// 删除B树T上结点p的关键字k
if(p->ptr[i-1]!=NULL) // 若不是最下层非终端结点
{
Successor(p, i); // 由后继最下层非终端结点的最小关键字代替它DeleteBTree(p, 1, m, T); // 变成删除最下层非终端结点中的最小关键字}
else // 若是最下层非终端结点
{
Remove(p, i); // 从结点p中删除key[i]
if(p->keynum< (m-1)/2) // 删除后关键字个数小于(m-1)/2
Restore(p, i, m, T); // 调整B树
}
}
//中序遍历B树
voidPrintBTree(BTree t)
{
int i = 1;
if(NULL != t)
{
while(i <= t ->keynum)
{
PrintBTree(t ->ptr[i-1]);
printf("%d ", t -> key[i]);
i++;
}
PrintBTree(t ->ptr[i-1]);
}
}
//销毁B树
voidDestroyBTree(BTree t)
{
int i = 1;
if(NULL != t)
{
while(i <= t ->keynum)
{
DestroyBTree(t ->ptr[i-1]);
free(t ->ptr[i-1]);
i++;
}
DestroyBTree(t ->ptr[i-1]);
}
//初始化B树
voidCreatBTree(BTree&T, int n, int m)
{
inti,j;
result p = NULL;
p = (restype *)malloc(sizeof(restype));
//srand( (unsigned)time( NULL ) ); //初始化随机数
if(0 == n)
printf("初始化成功,该B树为空树。
\n");
else
{
for(j = 0; j < n; j++)
{
i = rand()%1000;
SearchBTree(T, i, p); //查找插入位置
InsertBTree(T, i, p->pt, p->i, m); //进行插入
}
printf("初始化成功!\n");
}
}
intmenu_select() //选择菜单
{
int i;
printf("********************* B 树**********************\n"); printf("*******************menu select********************\n"); printf(" 1. 初始化B树\n");
printf(" 2. 查找元素\n");
printf(" 3. 插入元素\n");
printf(" 4. 删除元素\n");
printf(" 5. 打印B树\n");
printf(" 6. 销毁B树\n");
printf(" 0. 退出程序\n");
printf("**************************************************\n"); printf("***********By:2013级计算机科学与技术3班***********\n"); printf("*************** 3113005867 方典禹***************\n"); printf("**************************************************\n"); do
{
printf("\n\t请输入操作B树方法:\n");
scanf("%d",&i);
}while(i < 0 || i > 6); //此处为判断输入
return i; }。