抽象性設計——用C語言實現B樹的基本操作

這次做的是數據結構的一個抽象性實驗,我選擇的是B樹的基本操作。

編譯環境是:VS 2015



BTree.h

#include<stdio.h>
#include<stdlib.h>
#include<time.h>


#define TRUE 1
#define FALSE 0
#define OVERFLOW -1
#define OK 1
#define ERROR 0
#define M 20//定義階數最大值




typedef int KeyType;
typedef int Status;
typedef struct {  //記錄的結構定義
KeyType key;
char data;
}Record;


typedef struct BTNode{        //B樹結點類型定義
int keynum;      //結點中關鍵字個數,即結點的大小
KeyType key[M + 1]; //關鍵字,key[0]未用
struct BTNode *parent; //雙親結點指針
struct BTNode *ptr[M + 1];//孩子結點指針數組
Record *recptr[M + 1];    //記錄指針向量,0號單元未用
}BTNode,*BTree;  //B樹結點和B樹類型


typedef struct {
BTree pt;  //指向找到的結點
int i;  //1<=i<=m,在結點中的關鍵字位序
int tag;  //1:查找成功,0:查找失敗
}result,*resultPtr;  //B樹的查找結果類型


//接口定義


void CreatBTree(BTree&T, int n, int m);
/*
初始條件:初始化關鍵字個數n大於等於0,B樹的階數m大於3小於等於20
操作結果:構建一顆階數爲m,含有n個關鍵字的B樹
*/


void SearchBTree(BTree T, int k, result &r);
/*
初始條件:樹T存在
操作結果:在m階B數T上查找關鍵字k,返回p{pt,i,tag}
*/


void InsertBTree(BTree &T, int k, BTree q, int i,int m);
/*
初始條件:樹T存在
操作結果:在B樹T上結點p->pt的key[i]和key[i+1]之間插入關鍵字k
*/


void DeleteBTree(BTree p, int i, int m, BTree &T);
/*
初始條件:B樹上p結點存在
操作結果:刪除B樹T上結點p->pt的關鍵字k
*/


void PrintBTree(BTree T);
/*
初始條件:樹T存在
操作結果:中序遍歷B樹
*/


void DestroyBTree(BTree T);
/*
初始條件:樹T存在
操作結果:銷燬B樹
*/


int menu();
/*
輸出選擇菜單
*/



OperationDefine.cpp

#include "BTree.h"


void CreatBTree(BTree &T, int n, int m) {//構建一顆階數爲m,含有n個關鍵字的B樹(3<=m<=M,0<=n<=10000)
//創建B樹
int i, j;
resultPtr p = NULL;
p = (result*)malloc(sizeof(result));
srand((unsigned)time(NULL));
if (n == 0)
printf("已成功初始化一棵空樹。\n");
else {
for (j = 0; j < n; j++) {
i = rand() % 1000;//生成隨機數i
SearchBTree(T, i, *p);//查找i插入位置
InsertBTree(T, i, p->pt, p->i, m);  //進行插入
}
printf("創建B樹成功!\n");
}
}


void PrintBTree(BTree T) {
//中序遍歷B樹
int i = 1;
if (NULL != T) {
for (; i <= T->keynum; i++) {
PrintBTree(T->ptr[i - 1]);
printf("%d  ", T->key[i]);
}
PrintBTree(T->ptr[i - 1]);
}
}


int Search(BTree p, int k) {
int i = 1;
while (i <= p->keynum&&k > p->key[i])
i++;
return i;
}


void SearchBTree(BTree T, int k, result &r) {
//在m階B樹T上查找關鍵字k,返回(pt,i,tag)
//若查找成功,則特徵值tag=1,指針pt所致結點中第i個關鍵字等於k;否則
//特徵值tag=0,等於k的關鍵字記錄應插入在指針pt所指結點中第i-1個和第i個關鍵字間
int i = 0, found = 0;
BTree p = T, q = NULL;
while (p != NULL && 0 == found) {
i = Search(p, k);//在p->key[1..keynum]中查找p->key[i-1]<k<=p->p->key[i]
if (i > 0 && p->key[i] == k)
found = 1;//找到待查關鍵字
else{
q = p;
p = p->ptr[i - 1];
}
}
if (1 == found) {//查找成功
r.pt = p;
r.i = i;
r.tag = 1;
}
else {//查找不成功,返回key的插入位置i
r.pt = q;
r.i = i;
r.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;//將ap所有孩子結點指向ap
}
q->keynum = s - 1;//q結點的前一半保留,修改keynum
}


void newroot(BTree &T, BTree p, int x, BTree ap) {//生成新的根結點
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, BTree ap) {//x和ap分別插到q->key[i]和q->ptr[i]
int j, 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++;//關鍵字數+1
}


void InsertBTree(BTree &T, int k, BTree q, int i, int m) {
//在B樹T上q結點的key[i-1]和key[i]之間插入關鍵字k
//若引起結點過大,則沿雙親指針進行必要的結點分裂調整,使T仍是m階的B樹
int x, s, finished = 0, neednewroot = 0;
BTree ap;
if (NULL == q)//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]和q->ptr[i]
if (q->keynum < m) finished = 1;//插入完成
else {//分裂q結點
s = (m + 1) / 2;
split(q, s, ap);
x = q->key[s];
if (q->parent != NULL) {
q = q->parent;
i = Search(q, x);//在雙親結點中查找x的插入位置
}
else neednewroot = 1;
}
}//while
if (1 == neednewroot)//T是空樹或者根結點已分裂爲q和ap結點
newroot(T, q, x, ap);//生成含信息(q,x,ap)的新的根結點T
}
}


void Successor(BTree &p, int i) {//由後繼最下層非終端結點的最小關鍵字代替結點中關鍵字key[i]。
BTNode *temp;
temp = p->ptr[i];
for (; NULL != temp->ptr[0]; temp = temp->ptr[0]) ;//找出關鍵字的後繼
p->key[i] = temp->key[1];
p = temp;
}


void Remove(BTree &p, int i) {   //從結點p中刪除key[i]
int j;
int n = p->keynum;
for (j = i; j < n; j++) {  //關鍵字左移
p->key[j] = p->key[j + 1];
p->ptr[j] = p->ptr[j + 1];
}
p->keynum--;
}


void Restore(BTree &p, int i, int m, BTree &T) {//調整B樹
int j;
BTree ap = p->parent;
BTree lc, 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;
lc = ap->ptr[r - 1];
}
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) r++;//重新確定p在ap子樹的位置
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;
}
}


void DeleteBTree(BTree p, int i, int m, BTree &T) {
//刪除B樹上p結點第i個關鍵字
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樹
}
}


void DestroyBTree(BTree T) {
int i = 1;
if (NULL != T) {
for (; i <= T->keynum; i++) {
DestroyBTree(T->ptr[i - 1]);
free(T->ptr[i - 1]);
}
DestroyBTree(T->ptr[i - 1]);
}
}


int menu() {//菜單
int choice;
printf("\n\n\t\t\t|**********************************************|\n");
printf("\t\t\t|**********************************************|\n");
printf("\t\t\t _____________請先創建B樹再進行操作!__________\n");
printf("\t\t\t|                  B樹測試界面                 |\n");
printf("\t\t\t|                                              |\n");
printf("\t\t\t|   1.創建B樹            2.B樹結點的查找       |\n");
printf("\t\t\t|                                              |\n");
printf("\t\t\t|   3.B樹結點的插入      4.B樹結點的刪除       |\n");
printf("\t\t\t|                                              |\n");
printf("\t\t\t|   5.B樹的遍歷          6.B樹的銷燬           |\n");
printf("\t\t\t|                                              |\n");
printf("\t\t\t|   0.退出                                     |\n");
printf("\t\t\t|______________________________________________|\n");
printf("\t\t\t|**********************************************|\n");
printf("\t\t\t|**********************************************|\n");
printf("\t\t\t|              15軟件工程(4)班                 |\n");
printf("\t\t\t|                 3115005372                   |\n");
printf("\t\t\t|                   楊宇傑                     |\n");
printf("\t\t\t|********************S**************************|\n");
do {
printf("\t\t\t請選擇功能(輸入1-6任意一個數字):");
scanf_s("%d", &choice);
} while (choice<0||choice>6);//避免非法輸入
return choice;
}



BTree_Test.cpp

#include<stdio.h>
#include"BTree.h"


int main() {
BTree T=NULL;
result r;
int choice, k, i, m, n;

do{
choice = menu();
if (choice >= 0 && choice <= 7) {
system("cls");//把菜單清除
switch (choice) {
case 1:
printf("請輸入B樹的階數m:(3<=m<=20)\n");
scanf_s("%d", &m);
printf("請輸入B樹的初始化關鍵字個數:(0<=n<=10000)\n");
scanf_s("%d", &n);
CreatBTree(T, n, m);
break;
case 2:
printf("請輸入要查找的關鍵字:\n");
scanf_s("%d", &k);
SearchBTree(T, k, r);
if (r.tag) {
printf("該關鍵字的位置爲該結點中第%d個關鍵字\n",r.i);
}
else {
printf("該關鍵字不存在!\n");
}
break;
case 3:
printf("請輸入要插入的關鍵字k:\n");
scanf_s("%d", &k);
SearchBTree(T, k, r);
InsertBTree(T, k, (&r)->pt, (&r)->i, m);
printf("插入成功!\n");
break;
case 4:
printf("請輸入要刪除B樹T上的關鍵字:\n");
scanf_s("%d", &i);
SearchBTree(T, i, r);
DeleteBTree(r.pt, r.i, m, T);
printf("刪除成功!\n");
break;
case 5:
printf("此時的B樹序列爲:\n");
PrintBTree(T);
printf("\n");
break;
case 6:
DestroyBTree(T);
printf("銷燬成功!\n");
break;
default:;
}
}
}while (choice > 0 && choice < 7);
return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章