C語言-B樹(B-樹)的完整實現
B樹的定義
B樹是一種平衡的多路查詢樹。
一顆m階B樹,或為空樹,或為滿足下列特性的m叉樹。
(1)樹中每個結點最多含有m棵子樹;
(2)若根結點不是葉子結點,則至少有兩顆子樹;
(3)除根之外的所有非終端結點至少有[m/2];
(4)每個非終端結點中包含資訊:(n,A0,K1,A1,K2,A2,…,Kn,An)。其中
①Ki(1≤i≤n)為關鍵字,且關鍵字按升序排序。
②指標Ai(0≤i≤n)指向子樹的根結點。
③關鍵字的個數n必須滿足:[m/2]-1≤n≤m-1
(5)所有的葉子結點都出現在同一層次上,並且不帶資訊(可以看作是外部節點或查詢失敗的結點,實際上這些結點不存在,指向這些結點的指標為空)
程式設計環境與配置
IDE:Dev-C++ 5.11
程式語言:C
程式結構圖
B樹的抽象資料型別定義
ADT BTree{
資料物件:D是具有相同特性的資料元素的集合
資料關係:R1={<ai-1,ai>|ai-1,ai∈D,i=2,...,n}
R2={<ptr[i-1],ptr[i]>|i=1...,n}
約定a1|key[1]為關鍵字陣列頭,an|key[p-<keynum]為關鍵字陣列尾
約定ptr[i]為結點的第i個子樹
基本操作:
InitBTree(t)
初始條件:B樹已定義
操作結果:初始化B樹
SearchBTNode(BTNode *p ,KeyType k)
初始條件:結點p已存在
操作結果:在結點p中查詢關鍵字k的插入位置i
Result SearchBTree(BTree t,KeyType k)
初始條件:B樹已存在
操作結果:在B樹查詢關鍵字k的插入位置,返回查詢結果
InsertBTNode(BTNode *&p,int i,KeyType k,BTNode *q)
初始條件:結點p和結點q已存在,0<i<p->keynum
操作結果:將關鍵字k和結點q分別插入到p->key[i+1]和p->ptr[i+1]中
SplitBTNode(BTNode *&p,BTNode *& q)
初始條件:結點p和結點q已存在
操作結果:將結點p分裂成兩個結點,前一半保留,後一半移入結點q
NewRoot(BTNode *&t,KeyType k,BTNode *p,BTNode *q)
初始條件:結點t,p,q已存在
操作結果:生成新的根結點t,原p和q為子樹指標
InsertBTree(BTree &t,int i,KeyType k,BTNode *p)
初始條件:結點p和結點t已存在,0<i<p->keynum
操作結果:在B樹t中插入關鍵字k
Remove(BTNode *p,int i)
初始條件:結點p已存在,0<i<p->keynum
操作結果:p結點刪除key[i]和它的孩子指標ptr[i]
Substitution(BTNode *p,int i)
初始條件:結點p已存在,0<i<p->keynum
操作結果:查詢替代值
MoveRight(BTNode *p,int i)
初始條件:結點p已存在,0<i<p->keynum
操作結果:結點調整右移操作
MoveLeft(BTNode *p,int i)
初始條件:結點p已存在,0<i<p->keynum
操作結果:結點調整左移操作
Combine(BTNode *p,int i)
初始條件:結點p已存在,0<i<p->keynum
操作結果:結點調整合並操作
AdjustBTree(BTNode *p,int i)
初始條件:結點p已存在,0<i<p->keynum
操作結果:B樹調整操作
BTNodeDelete(BTNode *p,KeyType k)
初始條件:結點p已存在
操作結果:在結點p中刪除關鍵字k
BTreeDelete(BTree &t,KeyType k)
初始條件:B樹t已存在
操作結果:在B樹t中刪除關鍵字k
DestroyBTree(BTree &t)
初始條件:B樹t已存在
操作結果:遞迴釋放B樹
PrintBTree(BTree t)
初始條件:B樹t已存在
操作結果:遍歷列印B樹
}ADT BTree
標頭檔案
定義了需要用到的資料型別,結構體型別,以及所有函式介面;
//==========ADT BTree的表示與實現==========
#ifndef _BTREE_H
#define _BTREE_H
#define MAXM 10 //定義B樹的最大的階數
const int m=4; //設定B樹的階數
const int Max=m-1; //結點的最大關鍵字數量
const int Min=(m-1)/2; //結點的最小關鍵字數量
typedef int KeyType; //KeyType為關鍵字型別
//===============B樹儲存結構==============
typedef struct node{ //B樹和B樹結點型別
int keynum; //結點關鍵字個數
KeyType key[MAXM]; //關鍵字陣列,key[0]不使用
struct node *parent; //雙親結點指標
struct node *ptr[MAXM]; //孩子結點指標陣列
}BTNode,*BTree;
typedef struct{ //B樹查詢結果型別
BTNode *pt; //指向找到的結點
int i; //在結點中的關鍵字位置;
int tag; //查詢成功與否標誌
}Result;
typedef struct LNode{ //連結串列和連結串列結點型別
BTree data; //資料域
struct LNode *next; //指標域
}LNode, *LinkList;
typedef enum status{ //列舉型別(依次遞增)
TRUE,
FALSE,
OK,
ERROR,
OVERFLOW,
EMPTY
}Status;
//============基本操作的函式原型宣告=============
Status InitBTree(BTree &t);
//初始化B樹
int SearchBTNode(BTNode *p,KeyType k);
//在結點p中查詢關鍵字k的插入位置i
Result SearchBTree(BTree t,KeyType k);
/*在樹t上查詢關鍵字k,返回結果(pt,i,tag)。若查詢成功,則特徵值
tag=1,關鍵字k是指標pt所指結點中第i個關鍵字;否則特徵值tag=0,
關鍵字k的插入位置為pt結點的第i個*/
void InsertBTNode(BTNode *&p,int i,KeyType k,BTNode *q);
//將關鍵字k和結點q分別插入到p->key[i+1]和p->ptr[i+1]中
void SplitBTNode(BTNode *&p,BTNode *&q);
//將結點p分裂成兩個結點,前一半保留,後一半移入結點q
void NewRoot(BTNode *&t,KeyType k,BTNode *p,BTNode *q);
//生成新的根結點t,原結點p和結點q為子樹指標
void InsertBTree(BTree &t,int i,KeyType k,BTNode *p);
/*在樹t上結點q的key[i]與key[i+1]之間插入關鍵字k。若引起
結點過大,則沿雙親鏈進行必要的結點分裂調整,使t仍是B樹*/
void Remove(BTNode *p,int i);
//從p結點刪除key[i]和它的孩子指標ptr[i]
void Substitution(BTNode *p,int i);
//查詢被刪關鍵字p->key[i](在非葉子結點中)的替代葉子結點(右子樹中值最小的關鍵字)
void MoveRight(BTNode *p,int i);
/*將雙親結點p中的最後一個關鍵字移入右結點q中
將左結點aq中的最後一個關鍵字移入雙親結點p中*/
void MoveLeft(BTNode *p,int i);
/*將雙親結點p中的第一個關鍵字移入結點aq中,
將結點q中的第一個關鍵字移入雙親結點p中*/
void Combine(BTNode *p,int i);
/*將雙親結點p、右結點q合併入左結點aq,
並調整雙親結點p中的剩餘關鍵字的位置*/
void AdjustBTree(BTNode *p,int i);
//刪除結點p中的第i個關鍵字後,調整B樹
int FindBTNode(BTNode *p,KeyType k,int &i);
//反映是否在結點p中是否查詢到關鍵字k
int BTNodeDelete(BTNode *p,KeyType k);
//在結點p中查詢並刪除關鍵字k
void BTreeDelete(BTree &t,KeyType k);
//構建刪除框架,執行刪除操作
void DestroyBTree(BTree &t);
//遞迴釋放B樹
Status InitQueue(LinkList &L);
//初始化佇列
LNode* CreateNode(BTree t);
//新建一個結點
Status Enqueue(LNode *p,BTree t);
//元素q入佇列
Status Dequeue(LNode *p,BTNode *&q);
//出佇列,並以q返回值
Status IfEmpty(LinkList L);
//佇列判空
void DestroyQueue(LinkList L);
//銷燬佇列
Status Traverse(BTree t,LinkList L,int newline,int sum);
//用佇列遍歷輸出B樹
Status PrintBTree(BTree t);
//輸出B樹
void Test();
//測試B樹功能函式
#endif
B樹具體介面實現
2.4.1InitBTree函式
功能:初始化B樹
程式碼實現:
Status InitBTree(BTree &t){
t=NULL;
return OK;
}
2.4.2SearchBTNode函式
功能:在結點p中查詢關鍵字k的插入位置i
程式碼實現:
int SearchBTNode(BTNode *p,KeyType k){
int i=0;
for(i=0;i<p->keynum&&p->key[i+1]<=k;i++);
return i;
}
2.4.3SearchBTree函式
功能:在樹t中查詢關鍵字k,返回查詢結果型別
程式碼實現:
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&&p->key[i]==k) //找到待查關鍵字
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的位置(或插入位置)
}
2.4.4InsertBTNode函式
功能:關鍵字k和結點q分別插入到p->key[i+1]和p->ptr[i+1]中
程式碼實現:
void InsertBTNode(BTNode *&p,int i,KeyType k,BTNode *q){
int j;
for(j=p->keynum;j>i;j--){ //整體後移空出一個位置
p->key[j+1]=p->key[j];
p->ptr[j+1]=p->ptr[j];
}
p->key[i+1]=k;
p->ptr[i+1]=q;
if(q!=NULL)
q->parent=p;
p->keynum++;
}
2.4.5SplitBTNode函式
功能:將結點p分裂成兩個結點,前一半保留,後一半移入結點q
程式碼實現:
void SplitBTNode(BTNode *&p,BTNode *&q){
int i;
int s=(m+1)/2;
q=(BTNode *)malloc(sizeof(BTNode)); //給結點q分配空間
q->ptr[0]=p->ptr[s]; //後一半移入結點q
for(i=s+1;i<=m;i++){
q->key[i-s]=p->key[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
}
2.4.6NewRoot函式
功能:生成新的根結點t,原p和q為子樹指標
程式碼實現:
void NewRoot(BTNode *&t,KeyType k,BTNode *p,BTNode *q){
t=(BTNode *)malloc(sizeof(BTNode)); //分配空間
t->keynum=1;
t->ptr[0]=p;
t->ptr[1]=q;
t->key[1]=k;
if(p!=NULL) //調整結點p和q的雙親指標
p->parent=t;
if(q!=NULL)
q->parent=t;
t->parent=NULL;
}
2.4.7InsertBTree函式
功能:在樹t中插入關鍵字k,返回插入結果
程式碼實現:
void InsertBTree(BTree &t,int i,KeyType k,BTNode *p){
BTNode *q;
int finish_tag,newroot_tag,s; //設定需要新結點標誌和插入完成標誌
KeyType x;
if(p==NULL) //t是空樹
NewRoot(t,k,NULL,NULL); //生成僅含關鍵字k的根結點t
else{
x=k;
q=NULL;
finish_tag=0;
newroot_tag=0;
while(finish_tag==0&&newroot_tag==0){
InsertBTNode(p,i,x,q); //將關鍵字x和結點q分別插入到p->key[i+1]和p->ptr[i+1]
if (p->keynum<=Max)
finish_tag=1; //插入完成
else{
s=(m+1)/2;
SplitBTNode(p,q); //分裂結點
x=p->key[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); //生成新根結點t,p和q為子樹指標
}
}
2.4.8Remove函式
功能:從p結點刪除key[i]和它的孩子指標ptr[i]
程式碼實現:
void Remove(BTNode *p,int i){
int j;
for(j=i+1;j<=p->keynum;j++){ //前移刪除key[i]和ptr[i]
p->key[j-1]=p->key[j];
p->ptr[j-1]=p->ptr[j];
}
p->keynum--;
}
2.4.9Substitution函式
功能:尋找替代值(右子樹中最小的關鍵字)
程式碼實現:
void Substitution(BTNode *p,int i){
BTNode *q;
for(q=p->ptr[i];q->ptr[0]!=NULL;q=q->ptr[0]);
p->key[i]=q->key[1]; //複製關鍵字值
}
2.4.10MoveRight函式
功能:雙親結點p中的最後一個關鍵字移入右結點q中
將左結點aq中的最後一個關鍵字移入雙親結點p中
程式碼實現:
void MoveRight(BTNode *p,int i){
int j;
BTNode *q=p->ptr[i];
BTNode *aq=p->ptr[i-1];
for(j=q->keynum;j>0;j--){ //將右兄弟q中所有關鍵字向後移動一位
q->key[j+1]=q->key[j];
q->ptr[j+1]=q->ptr[j];
}
q->ptr[1]=q->ptr[0]; //從雙親結點p移動關鍵字到右兄弟q中
q->key[1]=p->key[i];
q->keynum++;
p->key[i]=aq->key[aq->keynum]; //將左兄弟aq中最後一個關鍵字移動到雙親結點p中
p->ptr[i]->ptr[0]=aq->ptr[aq->keynum];
aq->keynum--;
}
2.4.11MoveLeft函式
功能:將雙親結點p中的第一個關鍵字移入左結點aq中,
將右結點q中的第一個關鍵字移入雙親結點p中
程式碼實現:
void MoveLeft(BTNode *p,int i){
int j;
BTNode *aq=p->ptr[i-1];
BTNode *q=p->ptr[i];
aq->keynum++; //把雙親結點p中的關鍵字移動到左兄弟aq中
aq->key[aq->keynum]=p->key[i];
aq->ptr[aq->keynum]=p->ptr[i]->ptr[0];
p->key[i]=q->key[1]; //把右兄弟q中的關鍵字移動到雙親節點p中
q->ptr[0]=q->ptr[1];
q->keynum--;
for(j=1;j<=aq->keynum;j++){ //將右兄弟q中所有關鍵字向前移動一位
aq->key[j]=aq->key[j+1];
aq->ptr[j]=aq->ptr[j+1];
}
}
2.4.12Combine函式
功能:雙親結點p、右結點q合併入左結點aq,
並調整雙親結點p中的剩餘關鍵字的位置
程式碼實現:
void Combine(BTNode *p,int i){
int j;
BTNode *q=p->ptr[i];
BTNode *aq=p->ptr[i-1];
aq->keynum++; //將雙親結點的關鍵字p->key[i]插入到左結點aq
aq->key[aq->keynum]=p->key[i];
aq->ptr[aq->keynum]=q->ptr[0];
for(j=1;j<=q->keynum;j++){ //將右結點q中的所有關鍵字插入到左結點aq
aq->keynum++;
aq->key[aq->keynum]=q->key[j];
aq->ptr[aq->keynum]=q->ptr[j];
}
for(j=i;j<p->keynum;j++){ //將雙親結點p中的p->key[i]後的所有關鍵字向前移動一位
p->key[j]=p->key[j+1];
p->ptr[j]=p->ptr[j+1];
}
p->keynum--; //修改雙親結點p的keynum值
free(q); //釋放空右結點q的空間
}
2.4.13AdjustBTree函式
功能:刪除結點p中的第i個關鍵字後,調整B樹
程式碼實現:
void AdjustBTree(BTNode *p,int i){
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);
}
2.4.14BTNodeDelete函式
功能:在結點p中查詢並刪除關鍵字k
程式碼實現:
int BTNodeDelete(BTNode *p,KeyType k){
int i;
int found_tag; //查詢標誌
if(p==NULL)
return 0;
else{
found_tag=FindBTNode(p,k,i); //返回查詢結果
if(found_tag==1){ //查詢成功
if(p->ptr[i-1]!=NULL){ //刪除的是非葉子結點
Substitution(p,i); //尋找相鄰關鍵字(右子樹中最小的關鍵字)
BTNodeDelete(p->ptr[i],p->key[i]); //執行刪除操作
}
else
Remove(p,i); //從結點p中位置i處刪除關鍵字
}
else
found_tag=BTNodeDelete(p->ptr[i],k); //沿孩子結點遞迴查詢並刪除關鍵字k
if(p->ptr[i]!=NULL)
if(p->ptr[i]->keynum<Min) //刪除後關鍵字個數小於MIN
AdjustBTree(p,i); //調整B樹
return found_tag;
}
}
2.4.15BTreeDelete函式
功能:構建刪除框架,執行刪除操作
程式碼實現:
void BTreeDelete(BTree &t,KeyType k){
BTNode *p;
int a=BTNodeDelete(t,k); //刪除關鍵字k
if(a==0) //查詢失敗
printf(" 關鍵字%d不在B樹中\n",k);
else if(t->keynum==0){ //調整
p=t;
t=t->ptr[0];
free(p);
}
}
2.4.16DestroyBTree函式
功能:遞迴釋放B樹
程式碼實現:
void DestroyBTree(BTree &t){
int i;
BTNode* p=t;
if(p!=NULL){ //B樹不為空
for(i=0;i<=p->keynum;i++){ //遞迴釋放每一個結點
DestroyBTree(*&p->ptr[i]);
}
free(p);
}
t=NULL;
}
2.4.17InitQueue函式
功能:初始化佇列
程式碼實現:
Status InitQueue(LinkList &L){
L=(LNode*)malloc(sizeof(LNode)); //分配結點空間
if(L==NULL) //分配失敗
return OVERFLOW;
L->next=NULL;
return OK;
}
2.4.18CreateNode函式
功能:新建一個結點
程式碼實現:
LNode* CreateNode(BTNode *p){
LNode *q;
q=(LNode*)malloc(sizeof(LNode)); //分配結點空間
if(q!=NULL){ //分配成功
q->data=p;
q->next=NULL;
}
return q;
}
2.4.19Enqueue函式
功能:元素q入佇列
程式碼實現:
Status Enqueue(LNode *p,BTNode *q){
if(p==NULL)
return ERROR;
while(p->next!=NULL) //調至佇列最後
p=p->next;
p->next=CreateNode(q); //生成結點讓q進入佇列
return OK;
}
2.4.20Dequeue函式
功能:出佇列,並以q返回值
程式碼實現:
Status Dequeue(LNode *p,BTNode *&q){
LNode *aq;
if(p==NULL||p->next==NULL) //刪除位置不合理
return ERROR;
aq=p->next; //修改被刪結點aq的指標域
p->next=aq->next;
q=aq->data;
free(aq); //釋放結點aq
return OK;
}
2.4.21IfEmpty函式
功能:佇列判空
程式碼實現:
Status IfEmpty(LinkList L){
if(L==NULL) //佇列不存在
return ERROR;
if(L->next==NULL) //佇列為空
return TRUE;
return FALSE; //佇列非空
}
2.4.22DestroyQueue函式
功能:銷燬佇列
程式碼實現:
void DestroyQueue(LinkList L){
LinkList p;
if(L!=NULL){
p=L;
L=L->next;
free(p); //逐一釋放
DestroyQueue(L);
}
}
2.4.23Traverse函式
功能:用佇列遍歷輸出B樹
程式碼實現:
Status Traverse(BTree t,LinkList L,int newline,int sum){
int i;
BTree p;
if(t!=NULL){
printf(" [ ");
Enqueue(L,t->ptr[0]); //入隊
for(i=1;i<=t->keynum;i++){
printf(" %d ",t->key[i]);
Enqueue(L,t->ptr[i]); //子結點入隊
}
sum+=t->keynum+1;
printf("]");
if(newline==0){ //需要另起一行
printf("\n");
newline=sum-1;
sum=0;
}
else
newline--;
}
if(IfEmpty(L)==FALSE){ //l不為空
Dequeue(L,p); //出隊,以p返回
Traverse(p,L,newline,sum); //遍歷出隊結點
}
return OK;
}
2.4.24PrintBTree函式
功能:輸出B樹
程式碼實現:
Status PrintBTree(BTree t){
LinkList L;
if(t==NULL){
printf(" B樹為空樹");
return OK;
}
InitQueue(L); //初始化佇列
Traverse(t,L,0,0); //利用佇列輸出
DestroyQueue(L); //銷燬佇列
return OK;
}
2.4.25Test1函式
功能:測試B樹功能
程式碼實現:
void Test1(){
system("color 70");
BTNode *t=NULL;
Result s; //設定查詢結果
int j,n=15;
KeyType k;
KeyType a[]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
printf("建立一棵%d階B樹:\n",m);
for(j=0;j<n;j++){ //逐一插入元素
s=SearchBTree(t,a[j]);
if(s.tag==0)
InsertBTree(t,s.i,a[j],s.pt);
printf(" 第%d步,插入元素%d:\n ",j+1,a[j]);
PrintBTree(t);
}
printf("\n");
printf("刪除操作:\n"); //刪除操作
k=9;
BTreeDelete(t,k);
printf(" 刪除%d:\n ",k);
printf(" 刪除後的B樹: \n");
PrintBTree(t);
printf("\n");
k=1;
BTreeDelete(t,k);
printf(" 刪除%d:\n ",k);
printf(" 刪除後的B樹: \n");
PrintBTree(t);
printf("\n");
printf(" 遞迴釋放B樹\n"); //遞迴釋放B樹
DestroyBTree(t);
PrintBTree(t);
}
2.4.26Test2函式
功能:測試B樹功能
程式碼實現:
void Test2(){
int i,k;
system("color 70");
BTree t=NULL;
Result s; //設定查詢結果
while(1){
printf("此時的B樹:\n");
PrintBTree(t);
printf("\n");
printf("=============Operation Table=============\n");
printf(" 1.Init 2.Insert 3.Delete \n");
printf(" 4.Destroy 5.Exit \n");
printf("=========================================\n");
printf("Enter number to choose operation:_____\b\b\b");
scanf("%d",&i);
switch(i){
case 1:{
InitBTree(t);
printf("InitBTree successfully.\n");
break;
}
case 2:{
printf("Enter number to InsertBTree:_____\b\b\b");
scanf("%d",&k);
s=SearchBTree(t,k);
InsertBTree(t,s.i,k,s.pt);
printf("InsertBTree successfully.\n");
break;
}
case 3:{
printf("Enter number to DeleteBTree:_____\b\b\b");
scanf("%d",&k);
BTreeDelete(t,k);
printf("\n");
printf("DeleteBTree successfully.\n");
break;
}
case 4:{
DestroyBTree(t);
break;
printf("DestroyBTree successfully.\n");
}
case 5:{
exit(-1);
break;
}
}
}
}
三.功能測試
插入功能測試/遍歷功能測試
依次插入1-15進行測試輸出,結果如下:
由輸出的B樹可知,插入功能正常並且遍歷功能正常
3.2刪除功能測試
在之前插入1-15後進行刪除關鍵字的功能測試,選取9和1依次進行刪除測試,結果如下:
根據B樹的定義和該B樹的輸出,刪除功能正常
3.3釋放功能測試
在之前的基礎上進行遞迴釋放B樹功能測試,結果如下:
遍歷輸出的結果為B數為空樹,說明釋放功能正常
3.4其他功能測試
其他介面在以上功能中已經有所體現,均正常,不再一一呼叫測試。
四.思考與小結
錯誤總結
(1)在部分需要判空的地方沒有判空
(2)遞迴實現的時候多次爆棧
(3)插入分裂的SplitBTNode函式一開始寫的時候分裂成兩個
(4)刪除操作中的Combine函式的指標忘記調整
4.2部分優化
4.2.1輸出優化
在一開始輸出的時候選用的是括號輸出法(測試功能選用的值略有不同),在直觀上比較難的去分辨哪些是雙親結點的左右結點,因此在輸出函式上進行了優化
通過佇列遍歷,在每一次遍歷的過程中能夠,模擬層次遍歷,在B樹的結構上更加美觀,而且更容易看清楚B樹的結構
4.2.2測試介面優化
在保持原本介面不變的情況下,寫了Test2函式,自行建立和進行各種B樹的操作.
整個程式原始碼
標頭檔案
#ifndef _BTREE_H
#define _BTREE_H
#define MAXM 10 //定義B樹的最大的階數
const int m=4; //設定B樹的階數
const int Max=m-1; //結點的最大關鍵字數量
const int Min=(m-1)/2; //結點的最小關鍵字數量
typedef int KeyType; //KeyType為關鍵字型別
typedef struct node{ //B樹和B樹結點型別
int keynum; //結點關鍵字個數
KeyType key[MAXM]; //關鍵字陣列,key[0]不使用
struct node *parent; //雙親結點指標
struct node *ptr[MAXM]; //孩子結點指標陣列
}BTNode,*BTree;
typedef struct{ //B樹查詢結果型別
BTNode *pt; //指向找到的結點
int i; //在結點中的關鍵字位置;
int tag; //查詢成功與否標誌
}Result;
typedef struct LNode{ //連結串列和連結串列結點型別
BTree data; //資料域
struct LNode *next; //指標域
}LNode, *LinkList;
typedef enum status{ //列舉型別(依次遞增)
TRUE,
FALSE,
OK,
ERROR,
OVERFLOW,
EMPTY
}Status;
Status InitBTree(BTree &t); //初始化B樹
int SearchBTNode(BTNode *p,KeyType k); //在結點p中查詢關鍵字k的插入位置i
Result SearchBTree(BTree t,KeyType k); /*在樹t上查詢關鍵字k,返回結果(pt,i,tag)。若查詢成功,則特徵值
tag=1,關鍵字k是指標pt所指結點中第i個關鍵字;否則特徵值tag=0,
關鍵字k的插入位置為pt結點的第i個*/
void InsertBTNode(BTNode *&p,int i,KeyType k,BTNode *q); //將關鍵字k和結點q分別插入到p->key[i+1]和p->ptr[i+1]中
void SplitBTNode(BTNode *&p,BTNode *&q); //將結點p分裂成兩個結點,前一半保留,後一半移入結點q
void NewRoot(BTNode *&t,KeyType k,BTNode *p,BTNode *q); //生成新的根結點t,原結點p和結點q為子樹指標
void InsertBTree(BTree &t,int i,KeyType k,BTNode *p); /*在樹t上結點q的key[i]與key[i+1]之間插入關鍵字k。若引起
結點過大,則沿雙親鏈進行必要的結點分裂調整,使t仍是B樹*/
void Remove(BTNode *p,int i); //從p結點刪除key[i]和它的孩子指標ptr[i]
void Substitution(BTNode *p,int i); //查詢被刪關鍵字p->key[i](在非葉子結點中)的替代葉子結點(右子樹中值最小的關鍵字)
void MoveRight(BTNode *p,int i); /*將雙親結點p中的最後一個關鍵字移入右結點q中
將左結點aq中的最後一個關鍵字移入雙親結點p中*/
void MoveLeft(BTNode *p,int i); /*將雙親結點p中的第一個關鍵字移入結點aq中,
將結點q中的第一個關鍵字移入雙親結點p中*/
void Combine(BTNode *p,int i); /*將雙親結點p、右結點q合併入左結點aq,
並調整雙親結點p中的剩餘關鍵字的位置*/
void AdjustBTree(BTNode *p,int i); //刪除結點p中的第i個關鍵字後,調整B樹
int FindBTNode(BTNode *p,KeyType k,int &i); //反映是否在結點p中是否查詢到關鍵字k
int BTNodeDelete(BTNode *p,KeyType k); //在結點p中查詢並刪除關鍵字k
void BTreeDelete(BTree &t,KeyType k); //構建刪除框架,執行刪除操作
void DestroyBTree(BTree &t); //遞迴釋放B樹
Status InitQueue(LinkList &L); //初始化佇列
LNode* CreateNode(BTree t); //新建一個結點
Status Enqueue(LNode *p,BTree t); //元素q入佇列
Status Dequeue(LNode *p,BTNode *&q); //出佇列,並以q返回值
Status IfEmpty(LinkList L); //佇列判空
void DestroyQueue(LinkList L); //銷燬佇列
Status Traverse(BTree t,LinkList L,int newline,int sum); //用佇列遍歷輸出B樹
Status PrintBTree(BTree t); //輸出B樹
void Test(); //測試B樹功能函式
#endif
BTree程式碼
#include"BTREE.h"
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
Status InitBTree(BTree &t){
//初始化B樹
t=NULL;
return OK;
}
int SearchBTNode(BTNode *p,KeyType k){
//在結點p中查詢關鍵字k的插入位置i
int i=0;
for(i=0;i<p->keynum&&p->key[i+1]<=k;i++);
return i;
}
Result SearchBTree(BTree t,KeyType k){
/*在樹t上查詢關鍵字k,返回結果(pt,i,tag)。若查詢成功,則特徵值
tag=1,關鍵字k是指標pt所指結點中第i個關鍵字;否則特徵值tag=0,
關鍵字k的插入位置為pt結點的第i個*/
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,使得p->key[i]<=k<p->key[i+1]
if(i>0&&p->key[i]==k) //找到待查關鍵字
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){
//將關鍵字k和結點q分別插入到p->key[i+1]和p->ptr[i+1]中
int j;
for(j=p->keynum;j>i;j--){ //整體後移空出一個位置
p->key[j+1]=p->key[j];
p->ptr[j+1]=p->ptr[j];
}
p->key[i+1]=k;
p->ptr[i+1]=q;
if(q!=NULL)
q->parent=p;
p->keynum++;
}
void SplitBTNode(BTNode *&p,BTNode *&q){
//將結點p分裂成兩個結點,前一半保留,後一半移入結點q
int i;
int s=(m+1)/2;
q=(BTNode *)malloc(sizeof(BTNode)); //給結點q分配空間
q->ptr[0]=p->ptr[s]; //後一半移入結點q
for(i=s+1;i<=m;i++){
q->key[i-s]=p->key[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;