1. 程式人生 > >資料結構課設--用B樹實現圖書管理系統

資料結構課設--用B樹實現圖書管理系統

      此文章是分享一下上學期資料結構課程的課程設計,我選擇的是以B樹為資料結構,開發一個圖書管理系統,B樹的優點在於查詢快,增刪結點相對於連結串列或者順序表效率更好,因此用來儲存大量圖書資訊更加合適。(開發環境為:vs2015)

如需要完整工程檔案、說明文件以及可執行exe檔案,下載地址:點選開啟連結

下面貼出程式碼:

library.h

#include	"windows.h"
#include	"stdio.h"
#include	"conio.h"
#include	"stdlib.h"
#include	"malloc.h"
#include	"time.h"
#include    "string.h"
#include    "errno.h"


#define TRUE 1
#define FALSE 0
#define OVERFLOW -1
#define OK 1
#define ERROR 0
#define M 3//定義階數
#define Super_Manager_Code 5372  //超級管理員密碼
#define MAX_NAME_LEN 20//姓名長度
#define MAX_BKNAME_LEN 50
#define OK 1
#define ERROR 0
#define	bookinfomationfile "bookinfo.dat"	// 圖書資訊檔案
#define	borrowerfile "borrower.dat"		// 借閱者名單檔案
typedef int KeyType;
typedef int Status;

/*****************************書和借閱者結點儲存定義*************************/
typedef struct ReaderNode {//借閱者
	int IDnum;                      //ID號
	char reader_name[MAX_NAME_LEN]; //姓名
	ReaderNode *rnext;              //指向下一個借閱者的指標
}ReaderNode, *ReaderType;

typedef struct BookNode {
	int booknum;   //書號
	char bookname[MAX_BKNAME_LEN];  //書名
	char writer[MAX_NAME_LEN];      //作者名字
	int current;                    //現存量
	int total;						//總庫存
	int published_year;				//出版年份
	float price;					//價格
	ReaderType reader;				//借閱者連結串列指標
}BookNode, *BookType;				//圖書型別

typedef BookNode Record;			//將書的結點定義為記錄

/*******************************B樹儲存定義***************************/

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 result {
	BTNode *pt;				  //指向找到的結點
	int i;					  //1<=i<=m,在結點中的關鍵字位序
	int tag;				  //1:查詢成功,0:查詢失敗
}result, *resultPtr;		  //B樹的查詢結果型別



 /*******************************B樹介面定義***************************/


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

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

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

void BTreeTraverse(BTree T, void(*visit)(BTree));
/*
	初始條件:樹T存在
	操作結果:遍歷B樹
*/


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

void ShowBTree(BTree T, short x = 8);
/*
	以凹入表的形式輸出B樹
*/
int login();
/*
	登陸介面
*/

/*******************************圖書館介面定義***************************/
void InitLibrary(BTree &L);
/*
	初始化一棵空樹
*/
void InsertBook(BTree &L, BookType B, result res);
/*
	插入新的書籍
*/
int DeleteBook(BTree &L, BookType B);
/*
	刪除已存在的書籍,不存在則提示不存在
*/
int CanBorrow(BTree L, BookType B, ReaderType R);
/*
	判斷指定書籍能否出借
*/
void BorrowBook(BTree L, BookType B, ReaderType R);
/*
	書籍出借
*/
int ReturnBook(BTree L, int booknum, int IDnum, BookType &B, ReaderType &R);
/*
	書籍歸還
*/
void PrintH();
/*
	輸出表格的頭部
*/
void PrintB(BookType B);
/*
	輸出指定書籍的資訊
*/
void PrintT();
/*
	輸出表格的尾部
*/
void PrintAll(BTree p);
/*
	輸出B樹結點的全部關鍵字資訊
*/
void PrintBook(BookType B);
/*
	以表格形式輸出一本書的資訊
*/
void PrintAllbooks(BTree L);
/*
	輸出所有書的資訊
*/
int ShowBookinfo(BTree L, int booknum);
/*
	輸出指定某本書的資訊
*/
void PrintBorrower(ReaderType R);
/*
	輸出某本書的借閱者資訊
*/
void	Welcome(int color);
/*
	顯示歡迎介面
*/
void	Creat(BTree &L);
/*
	從檔案中讀取圖書資訊
*/

library.cpp
#include"Library.h"


void InitLibrary(BTree &L) {
	L = NULL;
}

void InsertBook(BTree &L, BookType B, result res) {
	//書庫L已存在,res包含B書在書庫L中的位置或應該插入的位置
	//如果書庫中已存在B書,則只將B書的庫存量增加,否則插入B書到書庫L中

	if (res.tag == 0) InsertBTree(L, B->booknum, res.pt, res.i, B);
	//書庫中不存在該書,則插入
	else {
		BookType b = res.pt->recptr[res.i];
		b->current = b->current + B->total;//現存量增加
		b->total = b->total + B->total;//總庫存增加
	}
}

int DeleteBook(BTree &L, BookType B) {
	//如果書庫中存在B書,則從書庫中刪除B書的資訊,並返回OK,否則返回ERROR
	if (DeleteBTree(L, B->booknum)) return OK;//刪除成功
	else return ERROR;//刪除失敗
}

int CanBorrow(BTree L, BookType B, ReaderType R) {
	//如果書庫中存在B書,若B書現存量大於0,則可出借返回OK,否則返回ERROR
	if(B->current>0) return TRUE;//現存量大於零
	else return FALSE;//其他情況均不允許出借
}

void	BorrowBook(BTree L, BookType B, ReaderType R)
// 書庫L存在,B書是書庫中的書並且可被讀者R借閱(已通過CanBorrow()判斷)
// 借出一本B書,登記借閱者R的資訊,改變現存量,記錄借書日期,最遲應還日期等資訊。
{
	ReaderType	r;
	if (!B->reader) B->reader = R;				// 無其他借閱者,則直接登記
	else {
		for (r = B->reader; r->rnext; r = r->rnext);
		r->rnext = R;							// 否則到借閱者連結串列尾,登記
	}
	B->current--;								// 現存量減1

}

int ReturnBook(BTree L, int booknum, int IDnum, BookType &B, ReaderType &R) {
	//booknum為還書書號,IDnum是借閱者的ID號
	//若書庫中不存在書號為booknum的書,則搜尋出錯,返回-1
	//若有借閱記錄,則登出該記錄,並用B和R返回圖書資訊和借閱者資訊並返回1
	//若沒有r借閱的記錄,則用B返回圖書資訊,並返回0
	result res = SearchBTree(L, booknum); //在B樹上按書號搜尋
	if (res.tag == 0) return -1;
	B = res.pt->recptr[res.i];
	ReaderType p = res.pt->recptr[res.i]->reader, pre = p;
	while (p) {
		if (pre == p&&p->IDnum == IDnum) {
			R = p;
			B->current++;//現存量加一
			return 1;
		}
		if (p->IDnum == IDnum) {//多個借書者
			R = p;
			pre->rnext = p->rnext;
			B->current++;//現存量+1
			return 1;
		}
		pre = p;
		p = p->rnext;
	}
	return 0;
}

void PrintH() {//表格頭部格式
	printf("\n");
	printf("|*********************************圖書基本資訊**********************************|\n"); 
	printf("|  書號  |       書名         |      作者     | 現存 | 總庫存 | 出版年份 | 定價 |\n");
}

void PrintB(BookType B) { //顯示B書基本資訊
	printf("|--------|--------------------|---------------|------|--------|----------|------|\n");
	printf("|  %-4d  |%-20s|%-15s|", B->booknum,B->bookname,B->writer);
	printf(" %-5d|   %-4d | %-6d   |%-6.1f|\n",  B->current, B->total, B->published_year, B->price);
}

void PrintT() {//表格底部格式
	printf("|--------|--------------------|---------------|------|--------|----------|------|\n");
}

void PrintAll(BTree p) {//顯示B樹每個結點的記錄資訊
	int i;
	for (i = 1; i <= p->keynum; ++i) {
		PrintB(p->recptr[i]);
	}
}

void PrintBook(BookType B) {//以表格形式顯示一本書的基本資訊
	PrintH();
	PrintB(B);
	PrintT();
	printf("\n");
}

void PrintBorrower(ReaderType R) {//以表格形式顯示一本書的借閱者資訊
	ReaderType p = R;
	printf("|------------------------|\n");
	while (p) {
		printf("|  %-5d  |  %-10s  |\n", p->IDnum, p->reader_name);
		p = p->rnext;
	}
	printf("|------------------------|\n");
}

void PrintAllbooks(BTree L) {//顯示書庫L所有圖書資訊
	PrintH();
	BTreeTraverse(L, PrintAll);
	PrintT();
}

int ShowBookinfo(BTree L, int booknum) {
	//若書庫L中存在書號為booknum的書,則現在該書的所有資訊並返回OK,書庫L不存在則
	//返回ERROR
	result res = SearchBTree(L, booknum);
	if (!res.tag) return ERROR;
	BookType B = res.pt->recptr[res.i];
	PrintBook(B);
	return OK;
}

void 	gotoxy(short x, short y)
// 移動游標到(x,y)座標,25>x>=0,80>y>=0	
{
	COORD point = { x, y };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);
}

void 	SetColor(unsigned short TextColor)
// 設定字型和背景顏色
{
	HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(hCon, TextColor);
}

void	Welcome(int color)
// 顯示歡迎介面
{
	SetColor(color);
	gotoxy(2, 5);
	printf("        ╭╮╭╮╭──╮╭╮  ╭──╮╭──╮╭╭╮╮╭──╮\n");
	printf("        │││││╭─╯││  │╭─╯│╭╮││  ││╭─╯\n");
	printf("        │╰╯││╰─╮││  ││  │││││││││╰─╮\n");
	printf("        │││││╭─╯││  ││  │││││╭╮││╭─╯\n");
	printf("        │  ││╰─╮│╰─╮│╰─╮│╰╯││││││╰─╮\n");
	printf("        ╰╰╯╯╰──╯╰──╯╰──╯╰──╯╰╯╰╯╰──╯\n");
	SetColor(11);
	printf("                      ╔══════════════╗\n");
	printf("                      ║    歡迎進入圖書管理系統    ║\n");
	printf("                      ╚══════════════╝\n");
	SetColor(color+1);
}

void	Creat(BTree &L) {
	// **********************************************************************************
	// 建立函式。當顯示選單時按8即可進入本建立函式。
	// L為書庫。
	// 從指定檔案讀入圖書資訊進行圖書入庫。
	// 然後從檔案讀入借閱者名字,用隨機數產生借閱證號和借閱書號,進行圖書借閱,並記錄日誌,
	// 接著讀入預約者名字,用隨機數產生借閱證號和圖書證號,進行圖書預約,並記錄日誌
	// 完成後即可返回主選單,進行還書、預約或其他測試。
	FILE		*fp;
	BookType	B;
	result		res;
	errno_t err;
	ReaderType	R;
	//fp = fopen_s(bookinfomationfile, "r");				// 開啟圖書資訊檔案
	if ((err = fopen_s(&fp, "bookinfo.dat", "r")) != 0)
	{
		printf("不能開啟圖書資訊檔案,請確認%s檔案已放到盤根目錄...", bookinfomationfile);
		getchar();
		return;
	}
	printf("\n下面將從檔案%s讀入圖書資訊。按任意鍵繼續...", bookinfomationfile);
	getchar();
	while (!feof(fp))								// 當未到檔案結束
	{
		B = (BookType)malloc(sizeof(BookNode));		// 申請圖書空間
		fscanf(fp, "%d %s %s %d %d %d %f", &B->booknum, B->bookname, B->writer, &B->current,
			&B->total, &B->published_year, &B->price);// 讀入圖書資料
		B->reader = NULL;			// 讀者和借閱者指標置空
		res = SearchBTree(L, B->booknum);			// 查詢插入位置
		if (!res.tag)								// 書庫中不存在該書,則插入
		{
			InsertBook(L, B, res);					// 插入
			printf("\n\n\n 插入下面的圖書");
			PrintBook(B);							// 顯示該圖書
			printf("\nB樹的狀態\n\n");
			ShowBTree(L);							// 顯示插入後B樹的狀態
		}
	}
	fclose(fp);
	printf("\n\n讀入的所有圖書資訊");
	PrintAllbooks(L);								// 顯示所有圖書
	printf("\n檔案圖書資訊讀入完畢!下面從檔案讀入借書者資訊,按任意鍵繼續...\n");
	getchar();
// **********************************************************************************
	system("cls");
	int		booknum, i = 0;
	fp = fopen(borrowerfile, "r");					// 開啟借閱者名單檔案
	if (!fp)
	{
		printf("\n\n\n不能開啟讀者資訊檔案,請確認%s檔案已放到盤根目錄...", borrowerfile);
		getch();
		return;
	}

	while (i++ <4000 && !feof(fp))
	{
		R = (ReaderType)malloc(sizeof(ReaderNode));
		fscanf(fp, "%6s", &R->reader_name);					// 從檔案讀入借閱者名字
		R->IDnum = rand() % 100;					// 用隨機數產生借閱證號
		R->rnext = NULL;					// 後續借閱者預約者指標置空
		booknum = rand() % 100 + 1;					// 隨機產生借閱書號
		res = SearchBTree(L, booknum);				// 搜尋二叉樹
		if (res.tag)								// 如果找到該書號
		{
			B = res.pt->recptr[res.i];
			if (CanBorrow(L, B, R))					// 判斷該借閱者是否可以借閱該書
			{
				BorrowBook(L, B, R);				// 如果可以則借出一本
			}
		}
	}

	fclose(fp);
	PrintAllbooks(L);							// 顯示所有圖書(可以看到現存量減少)
	printf("\n借書資訊讀入完畢,下面將從檔案讀入預約者資訊。按任意鍵繼續....");
	getch();
}

BTree_Operation.cpp
#include "Library.h"

/********************************B樹介面實現****************************/

int Search(BTree p, int k) {
	//在B樹p中查詢關鍵字k的位置i,使得p->node[i].key<=K<p->node[i+1].key
	int i;
	for (i = 0; i < p->keynum&&p->key[i + 1] <= k; i++);
	return i;
}

result	SearchBTree(BTree T, KeyType k)
// 在m階B樹上查詢關鍵字k,返回結果(pt,i,tag)。若查詢成功,則特徵值tag=1,指標pt
// 所指結點中第i個關鍵字等於k;否則返回特徵值tag=0,等於k的關鍵字應插入在pt所指結點
// 中第i和第i+1個關鍵字之間。
{
	int		i = 1;
	BTree	p = T, q = NULL;						// 初始化,p指向待查結點,q指向p的雙親
	int		found = FALSE;
	while (p && !found)
	{
		i = Search(p, k);							// 查詢k的位置使p->key[i]<=k<p->key[i+1]
		if (i> 0 && k == p->key[i])	found = TRUE;
		else {										// 未找到,則查詢下一層
			q = p;
			p = p->ptr[i];
		}
	}
	if (found) { result	r = { p, i, 1 };	return r; }	// 查詢成功
	else { result	r = { q, i, 0 };	return r; }	// 查詢不成功,返回k的插入位置資訊
}

void split(BTree &q, int s, BTree &ap) {
	//將q結點分裂成兩個結點,前一半保留,後一半移入新結點ap
	int i, n = q->keynum;
	ap = (BTNode*)malloc(sizeof(BTNode));//生成新結點ap
	ap->ptr[0] = q->ptr[s];
	for (i = s + 1; i <= M; i++) {//後一半移入ap結點
		ap->key[i-s] = q->key[i];
		ap->ptr[i-s] = q->ptr[i];
		ap->recptr[i - s] = q->recptr[i];
	}
	ap->keynum = n - s;//計算ap的關鍵字個數
	ap->parent = q->parent;
	for (i = 0; i <= M - 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 k, BTree ap,Record *recptr) {//生成新的根結點
	T = (BTNode*)malloc(sizeof(BTNode));
	T->keynum = 1;
	T->ptr[0] = p;
	T->ptr[1] = ap;
	T->key[1] = k;
	T->recptr[1] = recptr;  //T的子樹ap的父親指標
	if (p != NULL) p->parent = T;
	if (ap != NULL) ap->parent = T;
	T->parent = NULL;//新根的雙親是空指標
}

void Insert(BTree &q, int i, int k, BTree ap,Record *recptr) {//k和ap分別插到q->key[i+1]和q->ptr[i+1]
															//並插入關鍵字為k的記錄recprt
	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->recptr[j + 1] = q->recptr[j];
	}
	q->key[i+1] = k;//賦值
	q->ptr[i+1] = ap;
	q->recptr[i + 1] = recptr;
	if (ap != NULL) ap->parent = q;
	q->keynum++;//關鍵字數+1
}

Status InsertBTree(BTree &T, KeyType k, BTree q, int i, Record *rec)
//  在m階B樹T上結點*q的key[i]與key[i+1]之間插入關鍵字K和記錄rec。
//  若引起結點過大,則沿雙親鏈進行必要的結點分裂調整,使T仍是m階B樹。
{
	BTree ap = NULL;
	int finished = FALSE;
	if (!q)    newroot(T, NULL, k, NULL, rec);	// T是空樹,生成僅含關鍵字K的根結點*T
	else {
		while (!finished)
		{
			Insert(q, i, k, ap, rec);			// 將k和ap分別插入到q->key[i+1]和q->ptr[i+1]
			if (q->keynum < M) finished = TRUE; // 插入完成
			else {
				split(q, (M + 1) / 2, ap);			// 分裂結點Q
				k = q->key[(M + 1) / 2];
				rec = q->recptr[(M + 1) / 2];
				if (q->parent)
				{							// 在雙親結點*q中查詢k的插入位置
					q = q->parent;
					i = Search(q, k);
				}
				else finished = OVERFLOW;		// 根節點已分裂為*q和*ap兩個結點
			}
		}
		if (finished == OVERFLOW)				// 根結點已分裂為結點*q和*ap
			newroot(T, q, k, ap, rec);			// 需生成新根結點*T,q和ap為子樹指標
	}
	return OK;
} //  InsertBTree



void TakePlace(BTree &q, int &i) {
	//*q結點的第i個關鍵字為k,用q的後繼關鍵字代替q,且令q指向後繼所在結點
	BTree p = q;
	q = q->ptr[i];
	while (q->ptr[0]) q = q->ptr[0]; //查詢p的後繼
	p->key[i] = q->key[1]; //記錄代替
	p->recptr[i] = q->recptr[1];
	i = 1;  //代替後應該刪除q所指結點的第1個關鍵字
}

void Del(BTree q, int i) {
	//刪除q所指結點第i個關鍵字及其記錄
	for (; i < q->keynum; i++) {//關鍵字和記錄指標前移
		q->key[i] = q->key[i + 1];
		q->recptr[i] = q->recptr[i + 1];
	}
	q->keynum--;//關鍵字數目減1
}

Status	Borrow(BTree q)
// 若q的兄弟結點關鍵字大於(m-1)/2,則從兄弟結點上移最小(或最大)的關鍵字到雙親結點,
// 而將雙親結點中小於(或大於)且緊靠該關鍵字的關鍵字下移至q中,並返回OK,否則返回EREOR。
{
	int	i;
	BTree p = q->parent, b = NULL;					// p指向q的雙親結點
	for (i = 0; p->ptr[i] != q; i++);			// 查詢q在雙親p的子樹位置
	if (i >= 0 && i + 1 <= p->keynum && p->ptr[i + 1]->keynum > (M - 1) / 2)
	{										// 若q的右兄弟關鍵字個數大於(m-1)/2
		b = p->ptr[i + 1];						// b指向右兄弟結點
		q->ptr[1] = b->ptr[0];					// 子樹指標也要同步移動
		q->key[1] = p->key[i + 1];				// 從父節點借第i+1個關鍵字
		q->recptr[1] = p->recptr[i + 1];
		p->key[i + 1] = b->key[1];				// b第一個關鍵字上移到父節點
		p->recptr[i + 1] = b->recptr[1];
		for (i = 1; i <= b->keynum; i++)			// b第一個關鍵字上移,需把剩餘記錄前移一位
		{
			b->key[i] = b->key[i + 1];
			b->recptr[i] = b->recptr[i + 1];
			b->ptr[i - 1] = b->ptr[i];
		}
	}
	else if (i > 0 && p->ptr[i - 1]->keynum > (M - 1) / 2)
	{										// 若q的左兄弟關鍵字個數大約(m-1)/2
		b = p->ptr[i - 1];						// b指向左兄弟結點
		q->ptr[1] = q->ptr[0];
		q->ptr[0] = b->ptr[b->keynum];
		q->key[1] = p->key[i];					// 從父節點借第i個關鍵字
		q->recptr[1] = p->recptr[i];
		p->key[i] = b->key[b->keynum];			// 將b最後一個關鍵字上移到父節點
		p->recptr[i] = b->recptr[b->keynum];
	}
	else return ERROR;							// 無關鍵字大於(m-1)/2的兄弟
	q->keynum++;
	b->keynum--;
	for (i = 0; i <= q->keynum; i++)
		if (q->ptr[i]) q->ptr[i]->parent = q;	// 重新整理q的子結點的雙親指標
	return OK;
}

void Combine(BTree &q) {
	int i, j;
	BTree p = q->parent, b = NULL;//p指向q的父親
	for (i = 0; p->ptr[i] != q; i++);//查詢q在父親p中的子樹位置
	if (i == 0) {//若為0,則需合併為兄弟的第一個關鍵字
		b = p->ptr[i + 1];
			for (j = b->keynum; j >= 0; j--) {//將b的關鍵字和記錄後移一位
				b->key[j + 1] = b->key[j];
				b->recptr[j + 1] = b->recptr[j];
				b->ptr[j + 1] = b->ptr[j];
			}
			b->ptr[0] = q->ptr[0];//合併
			b->key[1] = p->key[1];
			b->recptr[1] = p->recptr[1];
		}
	else if (i > 0) {//若q在父親的子樹位置大於0,需合併為兄弟b的最後一個關鍵字
		b = p->ptr[i - 1];
		b->key[b->keynum + 1] = p->key[i];//合併
		b->recptr[b->keynum + 1] = p->recptr[i];
		b->ptr[b->keynum + 1] = q->ptr[0];
	}
	if(i==0||i==1)//若i為0或1,需將父結點p關鍵字前移一位
		for (; i < p->keynum; i++) {
			p->key[i] = p->key[i + 1];
			p->ptr[i] = p->ptr[i + 1];
			p->recptr[i] = p->recptr[i + 1];
		}
	p->keynum--;
	b->keynum++;
	free(q);
	q = b;   //q指向修改的兄弟結點
	for (i = 0; i <= b->keynum; i++)
		if (b->ptr[i]) b->ptr[i]->parent = b;//重新整理b的子結點的雙親指標
}


Status	DeleteBTree(BTree &T, KeyType k)
// 在m階B樹T上刪除關鍵字k及其對應記錄,並返回OK。
// 如T上不存在關鍵字k,則返回ERROR。
{
	KeyType	x = k;
	BTree	q, b = NULL;
	int		finished = FALSE, i = 1;
	result res = SearchBTree(T, k);				// 在T中查詢關鍵字k
	if (res.tag == 0) return ERROR;				// 未搜尋到
	else
	{
		q = res.pt;								// q指向待刪結點
		i = res.i;
		if (q->ptr[0]) TakePlace(q, i);			// 若q的子樹不空,(非底層結點)
												// 則以其後繼代之,且令q指向後繼所在結點
		Del(q, i);								// 刪除q所指向結點中第i個關鍵字及記錄
		if (q->keynum >= (M - 1) / 2 || !q->parent)		// 若刪除後關鍵字個數不小於(m-1)/2或q是根節點
		{
			finished = TRUE;					// 刪除完成
			if (q->keynum == 0) T = NULL;		// 若q的關鍵字個數為0 ,則為空樹
		}
		while (!finished)
		{
			if (Borrow(q))	finished = TRUE;	// 若q的相鄰兄弟結點關鍵字大於(m-1)/2,則從該
												// 兄弟結點上移一個最大(或最小)關鍵字到
												// 父節點,從父節點借一關鍵字到q
			else {								// 若q相鄰兄弟關鍵字個數均等於┌m /2┑-1
				Combine(q);	// 將q中的剩餘部分和雙親中的相關關鍵字合併至q的一個兄弟中
				q = q->parent;					// 檢查雙親
				if (q == T && T->keynum == 0)	// 若被刪結點的父節點是根T且T的關鍵字個數為0
				{
					T = T->ptr[0];				// 新根	
					T->parent = NULL;
					free(q);					// 刪除原雙親結點
					finished = TRUE;
				}
				else if (q->keynum >= M / 2) finished = TRUE;
			}								// 合併後雙親關鍵字個數不少於(m-1)/2,完成
		}
	}
	return OK;
}


void BTreeTraverse(BTree T, void (*visit)(BTree )) {
	//遍歷B樹T,對每個結點呼叫visit函式
	if (!T) return;
	visit(T);
	for (int i = 0; i <= T->keynum; ++i) {
		if (T->ptr[i]) BTreeTraverse(T->ptr[i], visit);
	}
}

void	ShowBTree(BTree T, short  x)
// 遞迴以凹入表形式顯示B樹T,每層的縮排量為x,初始縮排量為8
{
	if (!T)	return;
	int		i;
	printf("\n");
	for (i = 0; i <= x; i++) putchar(' ');			// 縮排x
	for (i = 1; i <= T->keynum; i++)
	{
		printf("%d,", T->key[i]);
	}
	for (i = 0; i <= T->keynum; i++)				// 遞迴顯示子樹結點關鍵字
		ShowBTree(T->ptr[i], x + 7);
}

int menu() {//選單
	int choice;
		printf("\n\n\t\t\t|**********************************************|\n");
		printf("\t\t\t|**********************************************|\n");
		printf("\t\t\t ______________________________________________|\n");
		printf("\t\t\t|                 圖書館管理系統               |\n");
		printf("\t\t\t|                                              |\n");
		printf("\t\t\t|   1.採編入庫           2.清除庫存            |\n");
		printf("\t\t\t|                                              |\n");
		printf("\t\t\t|   3.借閱圖書           4.歸還圖書            |\n");
		printf("\t\t\t|                                              |\n");
		printf("\t\t\t|   5.檢視圖書館全部圖書 6.檢視某圖書資訊      |\n");
		printf("\t\t\t|                                              |\n");
		printf("\t\t\t|   7.檢視某書借閱者資訊 8.讀取圖書資訊        |\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請選擇功能(輸入0-8任意一個數字):");
			scanf_s("%d", &choice);
		} while (choice < 0 || choice > 8);//避免非法輸入
		return choice;
}


void gotoxy(HANDLE hOut, int x, int y) {//定位游標
	COORD pos;
	pos.X = x;             //橫座標
	pos.Y = y;            //縱座標
	SetConsoleCursorPosition(hOut, pos);
}


int login() {//登陸介面
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//定義顯示器控制代碼變數
	int secret_code, i;	
	printf("\n\n\t\t\t ______________________________________\n");
	printf("\t\t\t|                                      |\n");
	printf("\t\t\t|                                      |\n");
	printf("\t\t\t|         請輸入圖書管理員密碼!       |\n");
	printf("\t\t\t|            密碼:____                |\n");//密碼為5372
	printf("\t\t\t|______________________________________|\n");
	gotoxy(hOut, 43, 6);
	scanf_s("%d", &secret_code);
	fflush(stdin);


	printf("\n\t\t\tLoding:");//進入動畫
	for (i = 0; i<11; i++) {
		Sleep(50);
		printf("█ ");
	}
	printf("\n\t\t\t載入完成,進入下一級系統");
	Sleep(1000);
	system("cls");//清屏

	if (secret_code == Super_Manager_Code)//判斷是不是管理員
		return 1;
	else
		return 0;
}
main.cpp
#include"Library.h"

int main() {
	BTree L = NULL;
	BookType B;
	result res;
	ReaderType R;
	int booknum, IDnum, k, book_num;
	char flag;
	int i;
	for (i = 0; i <= 10; i++){			// 顯示歡迎介面
		Welcome(i % 16);
		Sleep(100);
	}
	Sleep(800);
	system("cls");//清屏
	if (login()) {
		for (;;) {
			switch (menu())
			{
			case 1://採編圖書
				B = (BookType)malloc(sizeof(BookNode));
				B->reader = NULL;
				printf("請輸入要入庫的書號:\n");
				fflush(stdin);
				scanf_s("%d", &B->booknum);
				getchar();
				res = SearchBTree(L, B->booknum);
				if (!res.tag) {
					fflush(stdin);
					printf("請輸入該書書名:\n");
					fflush(stdin);
					gets_s(B->bookname);
					printf("請輸入該書作者:\n");
					fflush(stdin);
					gets_s(B->writer);
					printf("請輸入入庫數:\n");
					fflush(stdin);
					scanf_s("%d", &B->current);
					printf("請輸入出版年份:\n");
					fflush(stdin);
					scanf_s("%d", &B->published_year);
					printf("請輸入書本價格:\n");
					fflush(stdin);
					scanf_s("%f", &B->price);
					B->total = B->current;
					InsertBook(L, B, res);
					printf("用凹入表形式顯示B樹如下:\n");
					ShowBTree(L, 8);
					break;
				}
				else {//圖書已存在,只增加庫存
					printf("書庫已有該書記錄,請輸入增加的冊數:\n");
					fflush(stdin);
					scanf_s("%d", &B->total);
					InsertBook(L, B, res);
					B = res.pt->recptr[res.i];
					printf("用凹入表形式顯示B樹如下:\n");
					ShowBTree(L);
					printf("新書入庫操作完成!\n");
					system("pause");
					break;
				}


			case 2://清空庫存
				printf("請輸入要刪除的圖書書號:\n");
				fflush(stdin);
				scanf_s("%d", &booknum);
				res = SearchBTree(L, booknum);
				if (res.tag) {
					B = res.pt->recptr[res.i];
					PrintBook(B);
					printf("\n您確認刪除上面的圖書<Y確認,其餘按鍵返回主選單>?");
					fflush(stdin);
					getchar();
					flag = getchar();
					if (flag == 'Y' || flag == 'y') {
						DeleteBook(L, B);
						printf("\n清除完畢!\t刪除後用凹入表顯示B樹如下:\n");
						ShowBTree(L, 8);
					}
				}
				else {
					printf("\n書庫不存在此書,刪除失敗!\n");
				}
				printf("\t");
				system("pause");
				break;
			case 3://借閱圖書
				R = (ReaderType)malloc(sizeof(ReaderNode));
				R->rnext = NULL;
				printf("請輸入借閱圖書書號:\n");
				fflush(stdin);
				scanf_s("%d", &booknum);
				getchar();
				res = SearchBTree(L, booknum);
				if (res.tag) {
					B = res.pt->recptr[res.i];
					printf("請輸入您的姓名:\n");
					fflush(stdin);
					gets_s(R->reader_name);
					printf("請輸入您的ID號:\n");
					fflush(stdin);
					scanf_s("%d", &R->IDnum);
					if (CanBorrow(L, B, R)) {
						BorrowBook(L, B, R);
						printf("\n借書完成!");
					}
					else {
						printf("\n庫存不足,借閱失敗!");
						free(R);
					}
				}
				else {
					printf("\n庫存無此書,借閱失敗!");
					free(R);
				}
				printf("\t");
				system("pause");
				break;
			case 4://歸還圖書
				printf("\n請輸入圖書號:\n");
				scanf_s("%d", &booknum);
				printf("請輸入您的ID號:\n");
				scanf_s("%d", &IDnum);
				k = ReturnBook(L, booknum, IDnum, B, R);
				if (k == 1) {
					printf("\n還書完成!");
					R = NULL;
				}
				else if (k == 0) {
					printf("\n還書失敗,系統沒有您借該書記錄!");
				}
				else {
					printf("\n書庫中不存在此書!");
				}
				printf("\t");
				system("pause");
				break;
			case 5://輸出總庫存
				printf("圖書館總庫存資訊如下:\n");
				PrintAllbooks(L);
				break;
			case 6://查詢某種圖書
				printf("請輸入要查詢的圖書的編號:\n");
				scanf_s("%d", &book_num);
				ShowBookinfo(L, book_num);
				break;
			case 7://輸出某圖書的借閱者資訊
				printf("請輸入要查詢的圖書的編號:\n");
				scanf_s("%d", &book_num);
				res = SearchBTree(L, book_num);
				if (res.tag) {
					B = res.pt->recptr[res.i];
					printf("借閱者資訊如下:\n");
					PrintBorrower(B->reader);
				}
				else {
					printf("\n書庫不存在此書,查詢失敗!\n");
				}
				printf("\t");
				system("pause");
				break;
			case 8:Creat(L);
				system("cls");
				break;
			case 0:  //退出管理系統
				exit(0);
			default:
				break;
			}
		}
	}
	else {
		printf("密碼輸入錯誤!\n");
		system("pause");
	}
	return 0;
}

bookinfo.dat
35	燈下漫筆		魯迅		39	39	1997	19.5
16	BASIC語言		譚浩強		38	38	1980	18.0
18	C程式設計		譚浩強		40	40	2007	26.0
70	阿Q正傳			魯迅		28	28	1984	13.5
5	計算機組成原理		白中英		56	56	2008	39.0
50	意志力訓練手冊		美·弗蘭克	46	46	2005	20.0
22	打出速度-五筆教程	導向科技	45	45	2003	18.0
60	幽默大師		南琦		48	48	2007	38.5
13	魯迅小說選		魯迅		39	39	1976	18.3
17	計算機組成原理題庫	白中英		36	36	2008	18.5
12	墳			魯迅		55	55	1973	8.6
45	學會說話		南琦		57	57	2004	18.5
25	準風月談		魯迅		60	60	1973	13.9
42	超越自己		南琦		45	45	2007	15.0
15	魯迅雜文選		魯迅		30	30	1973	18.9
90	魯迅日文作品集		魯迅		45	45	1981	15.5
30	數位電子簡明教程	餘孟嘗		55	55	2005	35.0
7	資料結構(C語言版)	嚴蔚敏		60	60	2008	30.0

下面是程式截圖: