图书管理系统
#include "windows.h" #include "stdio.h" #include "stdlib.h" #include "conio.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define OVERFLOW -1 #define MAX_NAME_LEN 20 // 姓名最大长度 #define MAX_BKNAME_LEN 30 // 书名最大长度 #define MAX_BOOKS 100 // 书库中一个著者最多著作数 #define KEEP_DAYS 90 // 图书出借的期限 #define logfile "LibraryLogs.log" // 系统日志文件 typedef int Status; char *books[MAX_BOOKS]; // 某位著者著作名指针数组 char author[MAX_NAME_LEN]; // 著者姓名数组 int books_counter; // 著者著作计数 typedef struct ReaderNode // 借阅者 { int cardnum; // 借阅证号 char Readername[MAX_NAME_LEN]; // 借阅者姓名 union { struct { struct ReaderNode *nextr; // 下一个借阅者指针 }; struct { struct ReaderNode *nextb; // 下一个预约者指针 }; }; }ReaderNode,*ReaderType; // 读者类型 typedef struct BookNode // 图书结构体 { int booknum; // 书号 char bookname[MAX_BKNAME_LEN]; // 书名 char writer[MAX_NAME_LEN]; // 著者名 int current, total; // 现存量和总库存 int publishyear; // 出版年份 float price; // 定价 ReaderType reader; // 读者链表指针 ReaderType appointmenter; // 预约者链表指针 } BookNode,*BookType; // 图书类型 #define m 3 // 定义3叉B树 typedef BookNode Record; // 记录指针为图书结点类型 typedef int KeyType; typedef struct BTNode // B树结点 { int keynum; // 每个结点关键字个数 struct BTNode *parent; // 父亲指针 KeyType key[m+1]; // 关键字数组,0号单元未用 struct BTNode *ptr[m+1]; // 子数指针 Record *rec[m+1]; // 记录指针,0号单元未用 }BTNode,*BTree; // B树节点类型和B树类型 typedef BTree Library; typedef struct { BTNode *pt; // 指向找到的结点或应该插入的结点 int i; // 关键字序号 int tag; // 1表示查找成功,0表示查找失败 }Result; // B树查找结果类型 void NewRoot(BTree T, BTree p, KeyType k, BTree ap,Record *rec) // 当插入B树时T为空或根结点分裂为q和ap两个节点,需建立一个根节点空间 // 本函数为T申请一块空间,插入p,k,ap和记录rec { T = (BTree)malloc(sizeof(BTNode)); T->keynum = 1; T->ptr[0] = p; // 插入 T->ptr[1] = ap; T->key[1] = k; T->rec[1] = rec; if (p) p->parent= T; // 刷新T的子树ap的父亲指针 if (ap) ap->parent = T; T->parent = NULL; // 根节点双亲为NULL } void Insert(BTree q, int i, KeyType k, BTree ap, Record *rec) // 将k和ap分别插入到q->key[i+1]和q->ptr[i+1],并插入关键字为k的记录rec { int j; for (j = q->keynum;j > i; j--) // 记录、关键字、子树指针后移 { q->key[j+1] = q->key[j]; q->ptr[j+1] = q->ptr[j]; q->rec[j+1] = q->rec[j]; } q->key[i+1] = k; // 插入 q->ptr[i+1] = ap; q->rec[i+1] = rec; q->keynum ++; // 关键字个数增1 if (ap) ap->parent = q; // 刷新q的子树ap的父亲指针 } void Split(BTree q, int n, BTree ap) // 以n为分界将结点q分裂为q和ap2个结点 { int i; ap = (BTree)malloc(sizeof(BTNode)); // 新申请ap空间 ap->ptr[0] = q->ptr[n]; for (i = n+1;i <= m; i++) // q上n后的关键字、子树指针、记录转移到ap { ap->key[i-n] = q->key[i]; ap->ptr[i-n] = q->ptr[i]; ap->rec[i-n] = q->rec[i]; } ap->keynum = q->keynum - n; // 计算ap的关键字个数 q->keynum = n-1; // q的关键字个数减少 ap->parent = q->parent; for (i=0; i<=m-n; i++) if (ap->ptr[i]) ap->ptr[i]->parent = ap; // 刷新ap的子树的父亲指针 } int Search(BTree p, KeyType k) // 在B树p结点中查找关键字k的位置i,使key[i]<=k<key[i+1]) { int i; for (i=0; i < p->keynum && (p->key[i+1] < k||p->key[i+1] == k); i++); return i; } 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->rec[(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 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}; // 查找不成功,返回k的插入位置信息 return r; } } 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->rec[i] = q->rec[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->rec[i] = q->rec[i+1]; } q->keynum --; // 关键字数目减1 } Status Borrow(BTree q) // 若q的兄弟结点关键字大于(m-1)/2,则从兄弟结点上移最小(或最大)的关键字到双亲结点, // 而将双亲结点中小于(或大于)且紧靠该关键字的关键字下移至q中,并返回OK,否则返回EREOR。 { int i; BTree p = q->parent, b; // 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->rec[1] = p->rec[i+1]; p->key[i+1] = b->key[1]; // b第一个关键字上移到父节点 p->rec[i+1] = b->rec[1]; for (i =1 ;i <= b->keynum;i++) // b第一个关键字上移,需把剩余记录前移一位 { b->key[i] = b->key[i+1]; b->rec[i] = b->rec[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->rec[1] = p->rec[i]; p->key[i] = b->key[b->keynum]; // 将b最后一个关键字上移到父节点 p->rec[i] = b->rec[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) // 将q剩余部分和q的父结点的相关关键字合并到q兄弟中,然后释放q,令q指向修改的兄弟 { int i, j ; BTree p = q->parent, b; // 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->rec[j+1] = b->rec[j]; b->ptr[j+1] = b->ptr[j]; } b->ptr[0] = q->ptr[0]; // 合并 b->key[1] = p->key[1]; b->rec[1] = p->rec[1]; } else if (i > 0) // 若q在父亲的子树位置大约0 { // 需合并为兄弟b的最后一个关键字 b = p->ptr[i-1]; b->key[b->keynum+1] = p->key[i]; // 合并 b->rec[b->keynum+1] = p->rec[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->rec[i] = p->rec[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 ShowBTree(BTree T,short x) // 递归以凹入表形式显示B树T,每层的缩进量为x,初始缩进量为8 { int i; x = x+7; if (!T) return ; 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); } void InitLibrary(Library L) // 初始化书库L为空书库。 { L = NULL; } void InsertBook(Library 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->rec[res.i]; b->current = b->current + B->total; // 现存量和总库存增加 b->total = b->total + B->total; } } Status DeleteBook(Library L ,BookType B) // 如果书库中存在B书,则从书库中删除B书的信息,并返回OK,否则返回ERROR { if (DeleteBTree(L,B->booknum)) return OK; // 如果删除成功,返回OK else return ERROR; // 否则(删除不成功)返回ERROR } int BorrowBook(Library L ,BookType B ,ReaderType R) // 书库L存在,B书是书库中的书并且可被读者R借阅 // 借出一本B书,登记借阅者R的信息,改变现存量, { if (B->current > 0) // 若现存量大于0 { B->reader = R; B->current--; // 现存量减1 } return TRUE; } int ReturnBook(Library L ,int b ,int r,BookType B ,ReaderType R) // B为还书书号,R为还书者借阅证号, 若书库中不存在书号为B的书,则返回-1 // 若有R借阅B书的记录,则注销该记录,并用B和R返回图书信息和借阅者信息并返回1, // 若没有r借阅b书的记录,则用B返回图书信息,并返回0 { ReaderType pre,p; Result res = SearchBTree(L, b); // 搜索 if (!res.tag) return -1; // 未搜索到,返回-1 B = res.pt->rec[res.i]; // 用B记录图书信息 p=res.pt->rec[res.i]->reader; for ( ; p ;pre = p,p = p->nextr) // 搜索借书者链表 if (p->cardnum == r) // 找到则用R返回借阅者信息 { R = p; pre ->nextr = p->nextr; B->current++; // 现存量增1 return 1; } return 0; // 无该读者借阅该书信息则返回0 } void Menu() // 显示图书管理系统菜单 { system("cls"); printf("\n"); printf(" ╔══════════════╗\n"); printf(" ║ 欢迎使用图书管理系统 ║\n"); printf(" ╚══════════════╝\n"); printf(" \n\n\n"); printf("\t 友情提示:本系统可进行的操作如下(1-9):\n"); printf("\t *****************************\n"); printf("\t * * \n"); printf("\t * 1 新书入库 * \n"); printf("\t * * \n"); printf("\t * 2 清除库存 * \n"); printf("\t * * \n"); printf("\t * 3 图书出借 * \n"); printf("\t * * \n"); printf("\t * 4 图书归还 * \n"); printf("\t * * \n"); printf("\t * 5 退出系统 * \n"); printf("\t * * \n"); printf("\t ***************************** \n"); } void PrintH() // 打印图书表格表头 { printf("\n"); printf(" ╭═══════════════╮ \n"); printf("╭═════════║ 【 图书信息 】 ║════════════╮"); printf("║───┬─────╰═══════════════╯───┬────┬───║"); printf("║书号 │ 书名 │ 著者 │现存│总库存│出版年份│定价 ║"); } void PrintT() // 打印图书表格表尾 { printf("║───┼───────────┼──────┼──┼───┼────┼───║"); printf("╰══════════════════════════════════════╯\n"); } void PrintD(BookType B ) // 显示B书的基本信息。 { printf("║───┼───────────┼──────┼──┼───┼────┼───║"); printf("║ %-4d │《%s》",B->booknum, B->bookname); //gotoxy(32,wherey()); printf("│ %-11s│%-4d│ %-4d │%-6d │%-6.1f║",B->writer, B->current,B->total,B->publishyear,B->price); } void PrintBook(BookType B) // 以表格形式显示一本书的基本信息(书号,书名,著者,现存量,总库存量,出版年份,价格) { PrintH(); // 表头 PrintD(B); // 数据 PrintT(); // 表尾 printf("\n"); } int main() { Library L; int booknum,cardnum; char in; BookType B; Result res; ReaderType R; short x=8; //初始缩进量为8 int k; L=NULL; while (1) { Menu(); // 显示菜单 in = getch(); system("cls"); switch (in-'0') // 判断用户选择 { case 1: // 图书入库 while (in != 'M' && in != 'm') { B = (BookType)malloc(sizeof(BookNode)); B->reader = NULL; // 下一个借阅者指针置空 printf("\n\n\t请输入要入库的书号:"); scanf("%d",&B->booknum); res = SearchBTree(L, B->booknum); // 查找入库书号 if (res.tag) // 书库中已存在该书号的书 { PrintBook(res.pt->rec[res.i]); // 显示这本书 printf("\n\n\t该书已存在如上,请输入新增入库册数: "); fflush(stdin); scanf("%d",&B->total); InsertBook(L, B, res); // 该图书入库,数量增加 free(B); } else // 书库中不存在该书号,则插入到书库L中 { fflush(stdin); printf("\n\t请输入该书 书名: "); gets(B->bookname); printf("\n\t请输入该书著者: "); fflush(stdin); gets(B->writer); printf("\n\t请输入该书册数: "); fflush(stdin); scanf("%d",&B->current); B->total = B->current; printf("\n\t插入后B树如下:\n\n"); ShowBTree(L,x); // 显示插入后B树状态 } printf("\n\n\t图书入库完成,按M键返回主菜单,按其他任意键继续图书入库...."); in = getch(); } break; case 2: // 清除库存 while (in != 'M' && in != 'm') { printf("\n\n\t请输入要清除库存图书书号: "); scanf("%d",&booknum); res = SearchBTree(L, booknum); // 查找用户输入的书号 if (res.tag) // 如果查找到 { B = res.pt->rec[res.i]; PrintBook(B); // 显示找到的书 printf("\t确认删除上面的图书<Y/N>?"); // 提示是否确认删除 in = getch(); if (in == 'Y' || in == 'y') // 如果确认删除 { DeleteBook(L, B); // 删除图书 printf("\n\n\t图书%d从书库中清除完毕!\n\n\t删除后B树如下",booknum); ShowBTree(L,x); // 显示删除后B树状态 } } else printf("\n\n\t书库中不存在书号为%d的书!",booknum); printf("\n\n\t按'M'返回主菜单,按其他任意键继续清除库存..."); in = getch(); } break; case 3: // 图书出借 while (in != 'M' && in != 'm') { system("cls"); printf("\n\n\t请输入要借阅的图书书号: "); scanf("%d",&booknum); res = SearchBTree(L, booknum); // 在书库中搜索图书booknum if (res.tag) // 如果找到 { R = (ReaderType)malloc(sizeof(ReaderNode)); // 新申请一个读者空间 R->nextr = NULL; // 下一个借阅者指针置空 B = res.pt->rec[res.i]; printf("\n\n\t您查找的图书如下:"); PrintBook(B); // 显示找到的图书 printf("\n\n\t请输入您的借书证号:"); // 读入借阅者信息 scanf("%d",&R->cardnum); printf("\n\n\t请输入您的姓名: "); gets(R->Readername); if (BorrowBook(L, B, R)) // 如果该借阅者可以借阅该书 { printf("\n\n\t借书成功!"); } else { printf("\n\n\t对不起,您不能借阅该书!该书现存量少于0或已被他人预约。"); free(R); // 释放该读者空间 } } else printf("\n\n\t书库中不存在图书%d!",booknum); printf("\n\n\t按'M'返回主菜单,按其他任意键继续借阅图书..."); in = getch(); } break; case 4: // 图书归还 while (in != 'M' && in != 'm') { system("cls"); printf("\n\n\t请输入你要归还的图书号: "); scanf("%d",&booknum); printf("\n\n\t请输入你的借书证号: "); scanf("%d",&cardnum); k = ReturnBook(L, booknum, cardnum, B, R);// 为读者cardnum还书 if (k == 1) // 如果还书成功 { printf("\n\n\t还书成功!"); free(R); // 释放该读者借书记录 } else if (k == 0) // 如果没有该读者借阅该书的记录 { R = (ReaderType)malloc(sizeof(ReaderNode)); R->cardnum = cardnum; strcpy(R->Readername,"###"); printf("\n\n\t没有您借图书%d的记录!",booknum); free(R); } else printf("\n\n\t书库中不存在图书%d!",booknum); printf("\n\n\t按'M'返回主菜单,按其它任意键继续还书..."); in = getch(); } break; case 9: system("cls"); printf("\n\n\n\n\n\t退出系统,确认<Y/N>?..."); // 提示是否确认退出系统 in = getch(); if (in == 'y' ||in == 'Y') { // RecordLogs(8); // 记录日志-退出系统 exit(0); // 退出 } break; default: break; } } return 0; }