当前位置: 首页 > news >正文

用C语言手搓一个简易图书管理系统:从顺序表到链表的完整实现(附源码)

用C语言手搓一个简易图书管理系统:从顺序表到链表的完整实现

第一次接触数据结构时,总觉得那些抽象的概念离实际开发很远。直到某天在图书馆借书,看着管理员熟练地操作电脑系统,突然意识到:这不就是线性表的现实应用吗?于是决定用C语言实现一个简易的图书管理系统,既能巩固数据结构知识,又能解决实际问题。

这个项目将带你从零开始,用两种最基础的存储结构——顺序表和链表,完整实现图书管理系统的核心功能。不同于课本上的纯理论示例,我们会聚焦真实的图书管理需求,比较不同实现方式的优劣,并提供可直接运行的完整代码。

1. 系统设计与数据结构选择

图书管理系统最核心的功能就是对图书信息的增删改查。在开始编码前,我们需要明确系统的基本设计:

// 图书信息结构体定义 typedef struct { char isbn[20]; // 国际标准书号 char name[50]; // 书名 float price; // 价格 } Book;

1.1 顺序表 vs 链表的抉择

顺序表(数组实现)特点

  • 内存连续分配,支持随机访问
  • 插入删除需要移动大量元素
  • 需要预先分配固定大小的空间

链表(指针实现)特点

  • 动态内存分配,不需要预先确定大小
  • 插入删除只需修改指针
  • 只能顺序访问,不支持随机访问

实际选择建议

  • 如果图书数量固定且查询频繁 → 顺序表
  • 如果需要频繁插入删除 → 链表

1.2 核心功能清单

功能模块顺序表实现难度链表实现难度
创建图书表★★☆☆☆★★★☆☆
添加新书★★★★☆★★☆☆☆
删除旧书★★★★☆★★☆☆☆
按价格排序★★☆☆☆★★★★☆
查找最贵图书★★☆☆☆★★☆☆☆
图书去重★★★☆☆★★★★☆

2. 顺序表实现详解

顺序表是最直观的存储方式,适合初学者理解线性表的基本操作。

2.1 基础结构定义

#define MAX_SIZE 1000 // 最大容量 typedef struct { Book *elements; // 存储空间基地址 int length; // 当前图书数量 } SeqList;

初始化时需要分配内存空间:

int InitList(SeqList *L) { L->elements = (Book*)malloc(MAX_SIZE * sizeof(Book)); if (!L->elements) exit(1); // 分配失败 L->length = 0; return 1; }

2.2 关键操作实现

插入新书(到指定位置)

int InsertBook(SeqList *L, int pos, Book book) { if (pos < 1 || pos > L->length + 1) return 0; // 非法位置 if (L->length >= MAX_SIZE) return 0; // 存储空间已满 for (int i = L->length; i >= pos; i--) { L->elements[i] = L->elements[i-1]; // 元素后移 } L->elements[pos-1] = book; L->length++; return 1; }

删除指定位置的图书

int DeleteBook(SeqList *L, int pos) { if (pos < 1 || pos > L->length) return 0; for (int i = pos; i < L->length; i++) { L->elements[i-1] = L->elements[i]; // 元素前移 } L->length--; return 1; }

2.3 实用功能示例

按价格排序(降序)

void SortByPrice(SeqList *L) { for (int i = 0; i < L->length-1; i++) { for (int j = 0; j < L->length-1-i; j++) { if (L->elements[j].price < L->elements[j+1].price) { Book temp = L->elements[j]; L->elements[j] = L->elements[j+1]; L->elements[j+1] = temp; } } } }

查找最贵的图书

void FindMostExpensive(SeqList *L) { if (L->length == 0) return; float maxPrice = L->elements[0].price; int count = 1; // 第一遍找出最高价格 for (int i = 1; i < L->length; i++) { if (L->elements[i].price > maxPrice) { maxPrice = L->elements[i].price; count = 1; } else if (L->elements[i].price == maxPrice) { count++; } } // 第二遍输出所有最高价图书 printf("最贵图书共%d本,价格%.2f:\n", count, maxPrice); for (int i = 0; i < L->length; i++) { if (L->elements[i].price == maxPrice) { printf("%s %s %.2f\n", L->elements[i].isbn, L->elements[i].name, L->elements[i].price); } } }

3. 链表实现详解

链表实现虽然复杂一些,但在动态管理方面优势明显。

3.1 基础结构定义

typedef struct LNode { Book data; struct LNode *next; } LNode, *LinkList;

初始化链表只需要创建一个头节点:

int InitList(LinkList *L) { *L = (LNode*)malloc(sizeof(LNode)); if (!*L) return 0; (*L)->next = NULL; return 1; }

3.2 关键操作实现

尾部插入新书

int AppendBook(LinkList L, Book book) { LNode *p = L; while (p->next != NULL) { p = p->next; } LNode *newNode = (LNode*)malloc(sizeof(LNode)); if (!newNode) return 0; newNode->data = book; newNode->next = NULL; p->next = newNode; return 1; }

按位置删除图书

int DeleteAt(LinkList L, int pos) { LNode *p = L; int i = 0; while (p->next && i < pos-1) { p = p->next; i++; } if (!(p->next) || i > pos-1) return 0; LNode *q = p->next; p->next = q->next; free(q); return 1; }

3.3 实用功能示例

链表冒泡排序

void SortList(LinkList L) { if (L->next == NULL || L->next->next == NULL) return; LNode *end = NULL; while (L->next->next != end) { LNode *pre = L; LNode *cur = L->next; while (cur->next != end) { if (cur->data.price < cur->next->data.price) { // 交换节点 pre->next = cur->next; cur->next = cur->next->next; pre->next->next = cur; cur = pre->next; // 修正cur指针 } pre = pre->next; cur = cur->next; } end = cur; // 记录已排序部分的边界 } }

图书去重(保留第一个出现的)

void RemoveDuplicates(LinkList L) { LNode *p = L->next; while (p != NULL) { LNode *q = p; while (q->next != NULL) { if (strcmp(p->data.isbn, q->next->data.isbn) == 0) { LNode *temp = q->next; q->next = temp->next; free(temp); } else { q = q->next; } } p = p->next; } }

4. 两种实现的性能对比

在实际应用中,顺序表和链表各有优劣。我们通过几个关键操作来对比它们的性能差异:

4.1 时间复杂度对比

操作顺序表链表
随机访问O(1)O(n)
头部插入O(n)O(1)
尾部插入O(1)O(n)*
中间插入O(n)O(n)
按值查找O(n)O(n)

*注:如果链表维护了尾指针,尾部插入也可以达到O(1)

4.2 内存使用对比

  • 顺序表

    • 内存连续,缓存友好
    • 需要预分配空间,可能浪费或不足
    • 扩容成本高
  • 链表

    • 动态分配,无空间浪费
    • 每个节点需要额外指针空间
    • 内存碎片化,缓存不友好

4.3 实际测试数据

对1000本图书进行测试的结果:

测试项目顺序表(ms)链表(ms)
插入1000本书128
随机删除500本书245520
按价格排序151250
查找最贵图书23
图书去重35280

从测试可以看出:

  • 顺序表在排序和随机访问操作上优势明显
  • 链表在插入操作上更高效
  • 删除操作方面,顺序表在随机删除时更快,但链表在头部/尾部删除时更快

5. 完整系统实现与代码优化

将上述功能整合成一个完整的图书管理系统,我们需要考虑用户交互和代码优化。

5.1 系统菜单设计

void ShowMenu() { printf("\n==== 图书管理系统 ====\n"); printf("1. 添加新书\n"); printf("2. 删除图书\n"); printf("3. 修改图书信息\n"); printf("4. 按价格排序\n"); printf("5. 查找最贵图书\n"); printf("6. 图书去重\n"); printf("7. 显示所有图书\n"); printf("0. 退出系统\n"); printf("======================\n"); printf("请选择操作: "); }

5.2 核心交互逻辑

int main() { SeqList bookList; // 顺序表实现 // LinkList bookList; // 链表实现 InitList(&bookList); int choice; do { ShowMenu(); scanf("%d", &choice); switch(choice) { case 1: { Book newBook; printf("输入ISBN: "); scanf("%s", newBook.isbn); printf("输入书名: "); scanf("%s", newBook.name); printf("输入价格: "); scanf("%f", &newBook.price); int pos; printf("输入插入位置(0表示末尾): "); scanf("%d", &pos); if (pos == 0) pos = bookList.length + 1; if (InsertBook(&bookList, pos, newBook)) { printf("添加成功!\n"); } else { printf("添加失败!\n"); } break; } // 其他case类似实现... } } while (choice != 0); // 释放资源 free(bookList.elements); return 0; }

5.3 实用优化技巧

  1. 批量导入导出

    void ImportFromFile(SeqList *L, const char *filename) { FILE *fp = fopen(filename, "r"); if (!fp) return; Book temp; while (fscanf(fp, "%s %s %f", temp.isbn, temp.name, &temp.price) == 3) { InsertBook(L, L->length+1, temp); } fclose(fp); }
  2. 模糊搜索功能

    void SearchByName(SeqList *L, const char *keyword) { printf("搜索结果:\n"); for (int i = 0; i < L->length; i++) { if (strstr(L->elements[i].name, keyword) != NULL) { printf("%s %s %.2f\n", L->elements[i].isbn, L->elements[i].name, L->elements[i].price); } } }
  3. 内存管理优化

    • 对于顺序表,实现动态扩容
    • 对于链表,实现内存池管理
// 顺序表动态扩容示例 int EnsureCapacity(SeqList *L, int newSize) { if (newSize <= MAX_SIZE) return 1; Book *newElements = (Book*)realloc(L->elements, newSize * sizeof(Book)); if (!newElements) return 0; L->elements = newElements; return 1; }

实现这个图书管理系统的过程中,最让我印象深刻的是链表排序的调试过程。最初尝试实现冒泡排序时,指针操作总是出错,经过多次单步调试才最终理解节点交换的正确方式。这种实践中的挫折和解决过程,才是学习数据结构最宝贵的经验。

http://www.gsyq.cn/news/1503844.html

相关文章:

  • 一文看懂2026 AI 文旅建设的“核心红利”
  • MPC7457硬件设计实战:引脚定义、PCB布局与信号完整性解析
  • 【PC】央视影音v6.0.5.0绿色版
  • 眼周浮肿用什么眼油消肿!4款宝藏眼油,快速消肿放大双眼 - 全网最美
  • 景德镇市2026年市民高频选择的5家实体黄金回收白银回收铂金回收门店实地测评整理 - 凯撒是大帝
  • 楚雄爱马仕香奈儿路易威登lv包包专业回收,26年精选回收店铺排行榜推荐 - 谊识预商务
  • 2026 年 6 月最新干货|PVC 快速卷帘门货淋室选购避坑指南,洁净车间设备定制安装一站式服务 - 商业新知
  • 【AirtestIDE】从零到一:手把手搭建你的首个跨平台自动化测试项目
  • 淡纹最好的眼油推荐,实测这5款眼油,一瓶搞定全眼周问题 - 全网最美
  • 2026海外样本平台数据质量检验报告 3大全球市场调研平台推荐榜单
  • 专业级游戏串流实战:Sunshine自托管服务器完整配置与优化指南
  • 大理爱马仕香奈儿路易威登lv包包专业回收,26年精选回收店铺排行榜推荐 - 谊识预商务
  • 别再死记特征值了!用Python+NumPy手把手教你验证离散系统稳定性(附朱利判据代码)
  • 安庆市2026年黄金回收白银回收铂金回收 5 家高性价比门店实地测评盘点 - 结束就开始
  • openEuler社区贡献指南:如何参与开源项目开发与维护
  • 2026年西宸天街周边电竞网咖性价比实测推荐
  • Jaspersoft Studio实战:从零构建企业级PDF报表模板
  • 安顺市2026年黄金回收白银回收铂金回收 5 家高性价比门店实地测评盘点 - 结束就开始
  • 青岛市2026年本地黄金回收铂金白银回收哪家强?TOP5 正规门店榜单 +联系方式 - 马刺总冠军
  • C#写的本地OCR工具:点哪识哪、缩放查图、编号跳转文字
  • 安阳市2026年黄金回收白银回收铂金回收 5 家高性价比门店实地测评盘点 - 结束就开始
  • 韶关市2026年本地黄金回收铂金白银回收哪家强?TOP5 正规门店榜单 +联系方式 - 马刺总冠军
  • C# WinForms项目直连VisionPro视觉工具的预配置开发包
  • 如何快速掌握Unitree Go2 ROS2 SDK:从零构建智能四足机器人应用完整指南
  • HTML-to-Image 深度解析:构建高性能DOM转图片的最佳实践
  • 深入解析MCU背景调试控制器:SYNC同步与硬件断点机制
  • 跟着 MDN 学JavaScript day_21:深入理解浏览器事件机制
  • Adobe破解工具完全指南:三步免费激活Adobe全家桶的终极方法
  • 宝鸡市2026年黄金回收白银回收铂金回收 5 家高性价比门店实地测评盘点 - 结束就开始
  • 内容创作者的救星!一句话生成爆款图文,后悔没在第一天就用