基于C++的抽象数据类型B树的实现

一、实验概要

  • 实验项目名称: 抽象数据类型【B 树】的实现
  • 实验项目性质: 设计性实验
  • 所属课程名称: 数据结构

二、实验目的

对某组具体的抽象数据类型,运用课程所学的知识和方法,设计合理的数据结构,并在此基础上实现该抽象数据类型的全部基本操作。通过本设计性实验,检验所学知识和能力,发现学习中存在的问题。 进而达到熟练地运用本课程中的基础知识及技术的目的。

三、实验编程环境

编程环境:Vs Code

编程语言:C

四、实验要求(题目)

利用 C 语言数据类型表示 B 树的抽象数据类型,以及 B 树的抽象数据类型的实现。

抽象数据类型树的定义:树的结构定义和树的一组基本操作

4.1 B 树数据对象

B 树是一种平衡的多路查找树。

4.2 B 树的数据关系

一颗 m 阶 B 树,或为空树,或为满足下列特性的 m 叉树。

  • 树中每个结点最多含有 m 棵子树;
  • 若根结点不是叶子结点,则至少有两颗子树;
  • 除根之外的所有非终端结点至少有[m/2];
  • 每个非终端结点中包含信息:(n,A0,K1,A1,K2,A2,…,Kn,An)。其中
    • ①Ki(1≤i≤n)为关键字,且关键字按升序排序。
    • ② 指针 Ai(0≤i≤n)指向子树的根结点。
    • ③ 关键字的个数 n 必须满足:[m/2]-1≤n≤m-1
  • 所有的叶子结点都出现在同一层次上,并且不带信息(可以看作是外部节点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)

4.3 程序结构图

4.4 B 树的基本操作

void InitBTNode(BTree &p);

初始条件:结点 p 存在。

操作结果:初始化结点。

int InsertBTree(BTree &T, int k, Record rcd);

初始条件:结点 T 存在。

操作结果:在 B 树中插入关键字为 k 的记录 rcd

void split(BTree &q, BTree &ap);    //分裂饱和结点

初始条件:结点 q 和结点 ap 已存在。

操作结果:将结点 q 分裂成两个结点,前一半保留,后一半移入结点 ap。

void newRoot(BTree &T, BTree p, BTree ap, int k, Record rcd);

初始条件:结点 T,p,ap 已存在。

操作结果:生成新的根结点 T 原 p 和 ap 为子树指针

void Insert(BTree &q, int k, int index, BTree ap, Record rcd);  //插入关键字及指针ap

初始条件:结点 q 和结点 ap 已存在,0<indexkeynum

操作结果:将关键字 k 和结点 ap 分别插入到 q->key[index+1]和 q->ptr[index+1]中

int DeleteBTree(BTree &T, int key);         //删除索引为key的记录

初始条件:B 树 T 已存在

操作结果:在 B 树 T 中删除关键字 key

void Successor(BTree &node, int &index);

初始条件:B 树 node 已存在

操作结果:将直接前驱的索引和值覆盖掉当前结点

int Remove(BTree& node, int i);

初始条件:结点 node 已存在,0<ikeynum

操作结果:node 结点删除 key[i]和它的孩子指针 ptr[i]

int InsertRecord(BTree& node, int key,int i, Record rcd);

初始条件:结点 node 已存在,0<ikeynum

操作结果:向父结点插入关键字

void Restore(BTree& node, int index);

初始条件:结点 node 已存在,0<indexkeynum

操作结果:调整 node,使得 B 树正常

void CombineBTNode(BTree& l_node, BTree& r_node);

初始条件:结点 l_node,r_node 已存在

操作结果:将右节点数据调整至左节点,释放右节点

void DeleteRoot(BTree& root);

初始条件:结点 root 已存在

操作结果:将合并后结点中所有记录插入到父节点中

void Traverse(BTree t, int k);

初始条件:B 树 t 已存在

操作结果:遍历 B 树

void PrintBTree(BTree t);

初始条件:B 树 t 已存在

操作结果:遍历打印 B 树

void SearchBTree(BTree T, int k, result &r);

初始条件:结点 T 已存在

操作结果:在结点 T 中查找关键字 k 的插入位置 i,1) 用 r 返回(pt,i,tag)

int Search(BTree p, int k);

初始条件:结点 p 已存在

操作结果:在 p->key[1…p->keynum]中查找 p->key[i-1]<k<=p->key[i]

五、具体实现

5.1 公用头文件

公用头文件 includes.h:

#pragma once
#include <stdio.h>
#include <stdlib.h>

using namespace std;

#define m 4                     //B树的阶
#define MAX_NUM m               //关键字上限
#define MIN_NUM (m-1)/2         //除头结点外关键字下限
#define SPLIT_INDEX (m+1)/2     //分裂处下标
#define dataType int            //关键字数据类型

typedef struct{
    dataType data;      
}record, *Record;       //关键字类型

typedef struct BTNode{
    int keynum;                 //结点当前的关键字个数
    int key[m+1];               //索引数组,key[0]不用
    struct BTNode* parent;      //双亲结点指针
    struct BTNode* ptr[m+1];    //孩子结点指针数组
    Record recptr[m+1];         //记录指针向量,recptr[0]不用
}BTNode, *BTree;

typedef struct{
    int index;          //1<=index<=m,在结点中的关键字位序
    int tag;            //1:查找成功,0:查找失败
    BTNode* node;         //指向找到的结点
}result;
//insert
void InitBTNode(BTree &p);      //初始化结点
int InsertBTree(BTree &T, int k, Record rcd);   //插入结点
void split(BTree &q, BTree &ap);    //分裂饱和结点
void newRoot(BTree &T, BTree p, BTree ap, int k, Record rcd);   //创建新根结点
void Insert(BTree &q, int k, int index, BTree ap, Record rcd);  //插入关键字及指针ap


//delete
int DeleteBTree(BTree &T, int key);         //删除索引为key的记录
void Successor(BTree &node, int &index);    //若不是终端结点,将直接前驱的索引和值覆盖掉当前结点
int Remove(BTree& node, int i);     //从结点p中删除key[i]
int InsertRecord(BTree& node, int key,int i, Record rcd);   //向父结点插入关键字
void Restore(BTree& node, int index);   //调整树
void CombineBTNode(BTree& l_node, BTree& r_node);   //合并结点
void DeleteRoot(BTree& root);   //将合并后结点中所有记录插入到父节点中

//print
void Traverse(BTree t, int k); //遍历B树
void PrintBTree(BTree t);   //打印B树


//search
void SearchBTree(BTree T, int k, result &r);    //在m阶B树t上查找索引k,用r返回(pt,i,tag)
int Search(BTree p, int k); //在p->key[1..p->keynum]中查找p->key[i-1]<k<=p->key[i]

5.2 算法设计

void InitBTNode(BTree &p);

初始条件:结点 p 存在。

操作结果:初始化结点。

void InitBTNode(BTree &p){
    p = (BTree)malloc(sizeof(BTNode));
    //对p的子节点初始化
    for(int i = 0;i<=m+1;i++){
        p->key[i] = -1;
        p->ptr[i] = NULL;
        p->recptr[i] = NULL;
    }
    p->keynum = 0;
    p->parent = NULL;
    return;
}
int InsertBTree(BTree &T, int k, Record rcd);  

初始条件:结点 T 存在。

操作结果:在 B 树中插入关键字为 k 的记录 rcd

int InsertBTree(BTree &T, int k, Record rcd){
    result result;
    //查找是否已经存在
    SearchBTree(T, k, result);
    if(result.tag) return 0; //已存在则不执行插入
    Insert(result.node, k, result.index, NULL, rcd);
    BTree ap;
    while (result.node->keynum >= MAX_NUM  ){
		split(result.node, ap);
        k=result.node->key[SPLIT_INDEX]; 
		if (result.node->parent == NULL){
            //最顶层
            newRoot(T, result.node, ap, k, result.node->recptr[SPLIT_INDEX]);
            k = -1;
            result.node->recptr[SPLIT_INDEX] = NULL;
        }
		else {
			int index = Search(result.node->parent, k); //在双亲结点中查找k的插入位置
			Insert(result.node->parent, k, index, ap, result.node->recptr[SPLIT_INDEX]);
		}
        result.node = result.node->parent; //上移一层
        if(result.node==NULL) break;
    }
    return 1;
}
void split(BTree &q, BTree &ap);    //分裂饱和结点

初始条件:结点 q 和结点 ap 已存在。

操作结果:将结点 q 分裂成两个结点,前一半保留,后一半移入结点 ap。

void split(BTree &q, BTree &ap){
    //将q结点分裂成两个结点,前一半保留在原节点,另一半移入ap所指向的新节点
    int i, j, n = q->keynum;
    ap = (BTNode*)malloc(sizeof(BTNode));   //生成新结点
    InitBTNode(ap);
    ap->ptr[0] = q->ptr[SPLIT_INDEX];
    for(i = SPLIT_INDEX + 1, j = 1; i<=n;i++,j++){  //后一半移入ap结点
        ap->key[j] = q->key[i];
        ap->ptr[j] = q->ptr[i];
        ap->recptr[j] = q->recptr[i];
        q->key[i] = -1;
        q->ptr[i] = NULL;
        q->recptr[i] = NULL;
    }
    ap->keynum = n-SPLIT_INDEX;
    ap->parent = q->parent;
    for(i=0;i<=n-SPLIT_INDEX;i++)   //修改新结点的子节点的parent域
        if(ap->ptr[i]!=NULL) ap->ptr[i]->parent = ap;
    q->keynum = SPLIT_INDEX - 1;    //q的前一半保留,修改keynum
    return;
}
void newRoot(BTree &T, BTree p, BTree ap, int k, Record rcd);   

初始条件:结点 T,p,ap 已存在。

操作结果:生成新的根结点 T 原 p 和 ap 为子树指针

void newRoot(BTree &t, BTree p, BTree ap, int k, Record rcd){      //生成新的根节点 p是左子树 ap是右子树
    t = (BTNode*)malloc(sizeof(BTNode));
    t->keynum = 1;
    t->ptr[0] = p;
    t->ptr[1] = ap;
    t->key[1] = k;
    t->recptr[1] = rcd;
    if(p!=NULL) p->parent = t;
    if(ap!=NULL) ap->parent = t;
    t->parent = NULL;   //新根的双亲是空指针
}
void Insert(BTree &q, int k, int index, BTree ap, Record rcd);  //插入关键字及指针ap

初始条件:结点 q 和结点 ap 已存在,0<indexkeynum

操作结果:将关键字 k 和结点 ap 分别插入到 q->key[index+1]和 q->ptr[index+1]中

void Insert(BTree &q, int k, int index, BTree ap, Record rcd){
    //关键字k和新结点指针ap分别插入到q->key[i]和q->ptr[i]
    int i;
    //将待插入结点后的所有结点后移
    for(i = q->keynum; i>=index; i--){
        q->key[i+1] = q->key[i];
        q->ptr[i+1] = q->ptr[i];
        q->recptr[i+1] = q->recptr[i];
    }
    q->key[index] = k;
    q->ptr[index] = ap;
    q->recptr[index] = rcd;
    if(ap!=NULL) ap->parent = q;
    q->keynum++;
    return;
}        //删除索引为key的记录
int DeleteBTree(BTree &T, int key);         //删除索引为key的记录

初始条件:B 树 T 已存在

操作结果:在 B 树 T 中删除关键字 key

int DeleteBTree(BTree &T, int key){
	//删除索引为key的记录
	result r;
    SearchBTree(T, key, r);
	if (r.tag == 0) return 0;
	//若不是终端结点,将直接前驱的索引和值覆盖掉当前结点
	if (r.node->ptr[0] != NULL) Successor(r.node, r.index);
	//更新key的值为前驱结点
	key = r.node->key[r.index];
	//从结点p中删除key[i]
	Remove(r.node, r.index);
	//找到
	int index = Search(r.node->parent, key) - 1;
	//调整树
	if (r.node->parent != NULL && r.node->keynum < MIN_NUM)
        Restore(r.node, index);
	return 1;
}
void Successor(BTree &node, int &index);  

初始条件:B 树 node 已存在

操作结果:将直接前驱的索引和值覆盖掉当前结点

//若不是终端结点,将直接前驱的索引和值覆盖掉当前结点
void Successor(BTree &node, int &index){
    if(node == NULL) return;
	//寻找直接前驱
	if(node->ptr[index - 1] == NULL) return;
	BTree p = node->ptr[index - 1];
	while(p->ptr[p->keynum] != NULL)
        p = p->ptr[p->keynum];
	//将直接前驱的索引和值覆盖掉当前结点
	node->key[index] = p->key[p->keynum];
	node->recptr[index] = p->recptr[p->keynum];
	//令node指向待删除的直接前驱结点
	node = p;
	index = p->keynum;
}
int Remove(BTree& node, int i); 

初始条件:结点 node 已存在,0<ikeynum

操作结果:node 结点删除 key[i]和它的孩子指针 ptr[i]

//从结点p中删除key[i]
int Remove(BTree& node, int i){
	if (node == NULL) return 0;
	//将删除结点后结点前移
	for (; i < node->keynum; i++) {
		node->key[i] = node->key[i+1];
		node->recptr[i] = node->recptr[i+1];
	}
	node->keynum--;
	node->recptr[i] = NULL;
	return 1;
}
int InsertRecord(BTree& node, int key,int i, Record rcd);   

初始条件:结点 node 已存在,0<ikeynum

操作结果:向父结点插入关键字

//向父结点插入关键字
int InsertRecord(BTree& node, int key,int i, Record rcd) {
	if (node == NULL) return 0;
	for (int j = node->keynum; j >= i; j--) {
		node->key[j + 1] = node->key[j];
		node->recptr[j + 1] = node->recptr[j];
	}
	node->key[i] = key;
	node->recptr[i] = rcd;
	node->keynum++;
    return 1;
}
void Restore(BTree& node, int index);    

初始条件:结点 node 已存在,0<indexkeynum

操作结果:调整 node,使得 B 树正常

//调整树
void Restore(BTree& node, int index) {
	BTree parent,l_brother, r_brother;
	parent = node->parent;
	//左兄弟够借
	if (index > 0 && (l_brother = parent->ptr[index - 1])->keynum > MIN_NUM) {
		//该节点插入直接前驱
		Insert(node, parent->key[index], 1, node->ptr[0], parent->recptr[index]);
		//改变孩子指针
		node->ptr[0] = l_brother->ptr[l_brother->keynum];
		if (l_brother->ptr[l_brother->keynum] != NULL) l_brother->ptr[l_brother->keynum]->parent = node;
		//移除父节点中其直接前驱
		Remove(parent, index);
		//将直接前驱的直接前驱插入到父节点
		InsertRecord(parent, l_brother->key[l_brother->keynum], index, l_brother->recptr[l_brother->keynum]);
		//将直接前驱的直接前驱删除
		Remove(l_brother, l_brother->keynum);
	}
	//左兄弟不够,右兄弟够借
	else if (index < parent->keynum && (r_brother = parent->ptr[index + 1])->keynum > MIN_NUM) {
		Insert(node, parent->key[index+1], node->keynum + 1, r_brother->ptr[0], parent->recptr[index + 1]);
		Remove(parent, index + 1);
		InsertRecord(parent, r_brother->key[1], index + 1, r_brother->recptr[1]);
		Remove(r_brother, 1);
		for (int i=0; i <= r_brother->keynum; i++) r_brother->ptr[i] = r_brother->ptr[i + 1];
	}
	//兄弟不够借,合并左子树
	else if (index > 0) {
		l_brother = parent->ptr[index - 1];
		Insert(node, parent->key[index], 1, node->ptr[0], parent->recptr[index]);
		Remove(parent, index);
		CombineBTNode(l_brother, node);
		node = l_brother;
		for (int i = index; i <= parent->keynum; i++) parent->ptr[i] = parent->ptr[i + 1];
		//调整父节点至平衡
		if (parent->keynum < MIN_NUM) {
			if (parent->parent == NULL) DeleteRoot(parent);
			else Restore(parent, Search(parent->parent, node->key[1]) - 1);
		}
	}
	//兄弟不够借,合并右子树
	else {
		r_brother = parent->ptr[index + 1];
		Insert(node, parent->key[index+1],node->keynum + 1, r_brother->ptr[0], parent->recptr[index + 1]);
		Remove(parent, index + 1);
		CombineBTNode(node, r_brother);
		for (int i = index + 1; i <= parent->keynum; i++) 
            parent->ptr[i] = parent->ptr[i + 1];
		//调整父节点至平衡
		if (parent->keynum < MIN_NUM) {
			if (parent->parent == NULL) DeleteRoot(parent);
			else Restore(parent, Search(parent->parent, node->key[node->keynum + 1]) - 1);
		}
	}
}
void CombineBTNode(BTree& l_node, BTree& r_node); 

初始条件:结点 l_node,r_node 已存在

操作结果:将右节点数据调整至左节点,释放右节点

//合并结点
void CombineBTNode(BTree& l_node, BTree& r_node) {
	//左结点为空返回
	if (l_node == NULL) return;
	//将右结点所有记录合并到左结点
	for (int i = 1; i <= r_node->keynum; i++) {
		Insert(l_node, r_node->key[i], l_node->keynum + 1, r_node->ptr[i], r_node->recptr[i]);
	}
	//释放右节点
	free(r_node);
    return;
}
void DeleteRoot(BTree& root);   

初始条件:结点 root 已存在

操作结果:将合并后结点中所有记录插入到父节点中

//将合并后结点中所有记录插入到父节点中
void DeleteRoot(BTree& root) {
	BTree node = root->ptr[0];
	//父节点指向node的孩子
	root->ptr[0] = node->ptr[0];
	//非终端结点,孩子结点指向父节点
	if (node->ptr[0] != NULL) node->ptr[0]->parent = root;
	//将所有记录插入到父节点
	for (int i = 1; i <= node->keynum; i++) Insert(root, node->key[i], i, node->ptr[i], node->recptr[i]);
	//释放该节点
	free(node);
    return;
}
void Traverse(BTree t, int k); 

初始条件:B 树 t 已存在

操作结果:遍历 B 树

//遍历B树
void Traverse(BTree t, int k) {
	if (t != NULL) {
		int i;
		for (i = 1;i <= t->keynum;i++) {
			//非终端结点
			if (t->ptr[i - 1] != NULL) {  
				if (i == 1) {
					for (int j = 1;j <= (k * 2);j++) printf("  ");
					for (int j = 1;j <= t->keynum;j++) {
						if (j == t->keynum) printf("%d\n", t->key[j]);
						else printf("%d,", t->key[j]);
					}
					k++;
				}
			}
			//终端结点
			else {                       
				if (i == 1 && i == t->keynum) {
					for (int j = 1;j <= (k * 2);j++) printf("  ");
					printf("%d\n", t->key[i]);
				}
				if (i == 1 && i < t->keynum) {
					for (int j = 1;j <= (k * 2);j++) printf("  ");
					printf("%d,", t->key[i]);
				}
				if (i != 1 && i < t->keynum) {
					printf("%d,", t->key[i]);
				}
				if (i != 1 && i == t->keynum) {
					printf("%d\n", t->key[i]);
				}
			}
			if (i == 1)
				Traverse(t->ptr[i - 1], k);
			Traverse(t->ptr[i], k);  
		}
	}
}
void PrintBTree(BTree t); 

初始条件:B 树 t 已存在

操作结果:遍历打印 B 树

//凹入表输出
void PrintBTree(BTree t) {
	printf("当前B树的状态如下:\n");
	if (t == NULL)
		printf("此B树为空树\n");
	else
		Traverse(t, 0); 
}
int InsertBTree(BTree &T, int k, Record rcd);   

void InitBTNode(BTree &p){
    p = (BTree)malloc(sizeof(BTNode));
    //对p的子节点初始化
    for(int i = 0;i<=m+1;i++){
        p->key[i] = -1;
        p->ptr[i] = NULL;
        p->recptr[i] = NULL;
    }
    p->keynum = 0;
    p->parent = NULL;
    return;
}
void SearchBTree(BTree T, int k, result &r); 

初始条件:结点 T 已存在

操作结果:在结点 T 中查找关键字 k 的插入位置 i,1) 用 r 返回(pt,i,tag)

void SearchBTree(BTree t, int k, result &r){
    int i = 0, found = 0;
    //在m阶B树t上查找索引k,用r返回(pt,i,tag)
    //如果查找成功,则标记tag=1,指针pt所指结点中第i个索引等于k
    //否则tag=0,若要插入索引为k的记录,应位于pt结点中第i-1个和第i个索引之间
    BTree p = t, q = NULL;
    while(p!=NULL&&0==found){
        i = Search(p, k); //在p->key[1..p->keynum]中查找p->key[i-1]<k<=p->key[i]
        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.node = p; r.index = i; r.tag = 1;
    }else{  //查找不成功,返回k的插入位置q及i
        r.node = q; r.index = i; r.tag = 0;
    }
    return;
}
int Search(BTree p, int k);

初始条件:结点 p 已存在

操作结果:在 p->key[1…p->keynum]中查找 p->key[i-1]<k<=p->key[i]

//在p->key[1..p->keynum]中查找p->key[i-1]<k<=p->key[i]
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;
}

六、时间复杂度及优缺点

存储结构 B 树
InitBTNode(BTree &p); O(n)
InsertBTree(BTree &T, int k, Record rcd); O(n)
split(BTree &q, BTree &ap); O(n)
newRoot(BTree &T, BTree p, BTree ap, int k, Record rcd); O(1)
Insert(BTree &q, int k, int index, BTree ap, Record rcd); O(n)
DeleteBTree(BTree &T, int key); O(nlogn)
Successor(BTree &node, int &index); O(n)
Remove(BTree& node, int i); O(n)
InsertRecord(BTree& node, int key,int i, Record rcd); O(1ogn)
Restore(BTree& node, int index); O(1ogn)








CombineBTNode(BTree& l_node, BTree& r_node); O(logn)
DeleteRoot(BTree& root); O(logn)
Traverse(BTree t, int k); O(logn)
PrintBTree(BTree t); O(logn)
SearchBTree(BTree T, int k, result &r); O(nlogn)
Search(BTree p, int k); O(n)
void DestroyBTree(BTree &t) O(logn)




优点 在文件系统中有所应用,适合作为文件的索引;当查找的值恰好处在一个非叶子节点时,查找到该节点就会成功并结束查询
缺点 不适合使用磁盘读取

七、功能测试

7.1 插入功能

测试用例:

0 1 2 3 4 5 6
24 10 30 4 25 44 59

输出 B 树状态如下

7.2 删除功能

测试用例:

0 1 2 3 4 5 6
24 10 30 4 25 44 59

删除索引为 2 和 6 的数据,输出 B 树的状态

7.3 搜索功能

测试用例:

0 1 2 3 4 5 6
24 10 30 4 25 44 59

搜索索引为 1 和 6 的结点

7.4 释放 B 树

测试用例:

0 1 2 3 4 5 6
24 10 30 4 25 44 59

释放整颗 B 树

八、思考与小结

  • 在部分需要判空的地方没有判空

  • 本次基本实现了增删改查的全部功能,且实现可拓展 m 阶 B 树

  • 课本以及数据结构实验手册对于 B 树的介绍和代码过于缺少,因此本次抽象数据类型的实现最难的地方在于 B 树的学习与理解,利用哔哩哔哩教学与编程网站理解最后实现 B 树的接口,融会贯通,从测试结果来看,本次实验实现结果满意,可见对于 B 树的掌握较好,同时在编程细节上也有较好的体现,对于边界值,异常值,指针判空等都有注意到位。
    用磁盘读取 |

七、功能测试

7.1 插入功能

测试用例:

0 1 2 3 4 5 6
24 10 30 4 25 44 59

输出 B 树状态如下

7.2 删除功能

测试用例:

0 1 2 3 4 5 6
24 10 30 4 25 44 59

删除索引为 2 和 6 的数据,输出 B 树的状态

7.3 搜索功能

测试用例:

0 1 2 3 4 5 6
24 10 30 4 25 44 59

搜索索引为 1 和 6 的结点

7.4 释放 B 树

测试用例:

0 1 2 3 4 5 6
24 10 30 4 25 44 59

释放整颗 B 树

八、思考与小结

  • 在部分需要判空的地方没有判空

  • 本次基本实现了增删改查的全部功能,且实现可拓展 m 阶 B 树

  • 课本以及数据结构实验手册对于 B 树的介绍和代码过于缺少,因此本次抽象数据类型的实现最难的地方在于 B 树的学习与理解,利用哔哩哔哩教学与编程网站理解最后实现 B 树的接口,融会贯通,从测试结果来看,本次实验实现结果满意,可见对于 B 树的掌握较好,同时在编程细节上也有较好的体现,对于边界值,异常值,指针判空等都有注意到位。

猜你喜欢

转载自blog.csdn.net/newlw/article/details/125146010