我们来实现一个 **基于哈希查找的图书馆书籍管理系统**,使用 C 语言完成。该系统将结合:
- **线性表(顺序存储)** 存储完整的书籍信息;
- **哈希表 + 链地址法** 实现基于 ISBN 的快速查找;
- 支持:入库、检索、删除、遍历等功能。
---
### ✅ 功能要求总结
| 功能 | 描述 |
|------|------|
| 书籍入库 | 将新书加入线性表,并插入到哈希表中 |
| 书籍检索 | 按 ISBN 快速查找书籍信息 |
| 书籍删除 | 按 ISBN 删除书籍(线性表和哈希表同步更新) |
| 遍历所有书籍 | 按入库顺序输出所有书籍 |
| 冲突处理 | 使用链地址法(拉链法)解决哈希冲突 |
---
## 🧱 数据结构设计
我们将定义以下结构体:
1. `Book`:表示一本书的信息。
2. `Node`:哈希桶中的节点,保存对应书籍在线性表中的索引。
3. `HashTable`:哈希表,包含多个桶(每个桶是一个链表头指针)。
4. `Library`:主系统结构,包含线性表和哈希表。
---
## ✅ 完整代码实现(C语言)
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_NAME_LEN 50
#define HASH_TABLE_SIZE 20 // 哈希表大小
// 书籍结构
typedef struct {
int isbn;
char title[MAX_NAME_LEN];
char author[MAX_NAME_LEN];
} Book;
// 线性表结构(动态数组)
typedef struct {
Book* books;
int size; // 当前数量
int capacity; // 总容量
} BookList;
// 哈希表节点(链地址法)
typedef struct Node {
int index; // 在线性表中的索引
struct Node* next;
} HashNode;
// 哈希表结构
typedef struct {
HashNode* buckets[HASH_TABLE_SIZE];
} HashTable;
// 图书馆系统
typedef struct {
BookList bookList;
HashTable hashTable;
} Library;
// ============ 工具函数 ============
// 哈希函数:取模法
int hash(int isbn) {
return isbn % HASH_TABLE_SIZE;
}
// 初始化图书馆系统
void initLibrary(Library* lib) {
lib->bookList.books = NULL;
lib->bookList.size = 0;
lib->bookList.capacity = 0;
for (int i = 0; i < HASH_TABLE_SIZE; i++) {
lib->hashTable.buckets[i] = NULL;
}
}
// 动态扩容线性表
void resizeBookList(BookList* list) {
if (list->size >= list->capacity) {
list->capacity = (list->capacity == 0) ? 4 : list->capacity * 2;
list->books = (Book*)realloc(list->books, list->capacity * sizeof(Book));
if (!list->books) {
printf("内存分配失败!\n");
exit(1);
}
}
}
// 添加书籍到线性表并返回其索引
int addBookToList(BookList* list, int isbn, const char* title, const char* author) {
resizeBookList(list);
Book* b = &list->books[list->size];
b->isbn = isbn;
strncpy(b->title, title, MAX_NAME_LEN - 1);
b->title[MAX_NAME_LEN - 1] = '\0';
strncpy(b->author, author, MAX_NAME_LEN - 1);
b->author[MAX_NAME_LEN - 1] = '\0';
return list->size++; // 返回插入位置的索引
}
// 查找哈希表中是否存在某个 ISBN,并返回对应的线性表索引(-1 表示未找到)
int findInHashTable(HashTable* ht, int isbn) {
int h = hash(isbn);
HashNode* node = ht->buckets[h];
while (node != NULL) {
if (ht->buckets[h]->index < 0 || ht->buckets[h]->index >= ht->buckets[h]->index) continue;
if (ht->buckets[h]->index < 0 || ht->buckets[h]->index >= ht->buckets[h]->index) continue;
if (ht->buckets[h]->index < 0 || ht->buckets[h]->index >= ht->buckets[h]->index) continue;
if (lib->bookList.books[node->index].isbn == isbn) {
return node->index;
}
node = node->next;
}
return -1;
}
// 向哈希表插入记录(索引)
void insertIntoHashTable(HashTable* ht, int isbn, int index) {
int h = hash(isbn);
HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
if (!newNode) {
printf("内存分配失败!\n");
exit(1);
}
newNode->index = index;
newNode->next = ht->buckets[h];
ht->buckets[h] = newNode;
}
// 从哈希表中删除某 ISBN 对应的节点
int removeFromHashTable(HashTable* ht, int isbn) {
int h = hash(isbn);
HashNode* node = ht->buckets[h];
HashNode* prev = NULL;
while (node != NULL) {
if (lib->bookList.books[node->index].isbn == isbn) {
if (prev == NULL) {
ht->buckets[h] = node->next;
} else {
prev->next = node->next;
}
free(node);
return 1; // 成功删除
}
prev = node;
node = node->next;
}
return 0; // 未找到
}
// ========== 核心功能接口 ==========
// 书籍入库
void insertBook(Library* lib, int isbn, const char* title, const char* author) {
// 检查是否已存在
if (findInHashTable(&lib->hashTable, isbn) != -1) {
printf("错误:ISBN %d 已存在!\n", isbn);
return;
}
// 插入线性表
int index = addBookToList(&lib->bookList, isbn, title, author);
// 插入哈希表
insertIntoHashTable(&lib->hashTable, isbn, index);
printf("书籍 '%s' 入库成功(ISBN: %d)。\n", title, isbn);
}
// 书籍检索(按 ISBN)
void searchBookByISBN(Library* lib, int isbn) {
int index = findInHashTable(&lib->hashTable, isbn);
if (index == -1) {
printf("未找到 ISBN 为 %d 的书籍。\n", isbn);
return;
}
Book* b = &lib->bookList.books[index];
printf("找到书籍 -> ISBN: %d, 书名: %s, 作者: %s\n", b->isbn, b->title, b->author);
}
// 删除书籍(按 ISBN)
void deleteBookByISBN(Library* lib, int isbn) {
int index = findInHashTable(&lib->hashTable, isbn);
if (index == -1) {
printf("未找到 ISBN 为 %d 的书籍。\n", isbn);
return;
}
// 从哈希表中移除
removeFromHashTable(&lib->hashTable, isbn);
// 从线性表中删除:用最后一个元素覆盖当前元素(避免移动)
Book* books = lib->bookList.books;
books[index] = books[lib->bookList.size - 1]; // 覆盖被删项
lib->bookList.size--; // 缩小尺寸
// ❗❗ 删除后必须重建哈希表,因为索引已变化!
// 这是本设计的一个缺点:删除操作影响其他索引 → 需要重建哈希表
rebuildHashTable(lib);
printf("书籍 ISBN %d 已删除。\n", isbn);
}
// 重建哈希表(删除或修改索引后调用)
void rebuildHashTable(Library* lib) {
// 清空原有哈希表
for (int i = 0; i < HASH_TABLE_SIZE; i++) {
HashNode* node = lib->hashTable.buckets[i];
while (node != NULL) {
HashNode* temp = node;
node = node->next;
free(temp);
}
lib->hashTable.buckets[i] = NULL;
}
// 重新插入所有书籍
for (int i = 0; i < lib->bookList.size; i++) {
insertIntoHashTable(&lib->hashTable, lib->bookList.books[i].isbn, i);
}
}
// 遍历所有书籍(按入库顺序)
void traverseBooks(Library* lib) {
if (lib->bookList.size == 0) {
printf("图书馆为空。\n");
return;
}
printf("\n=== 所有书籍 ===\n");
for (int i = 0; i < lib->bookList.size; i++) {
Book* b = &lib->bookList.books[i];
printf("[%d] ISBN: %d, 书名: %s, 作者: %s\n", i+1, b->isbn, b->title, b->author);
}
printf("=================\n\n");
}
// 销毁图书馆(释放内存)
void destroyLibrary(Library* lib) {
free(lib->bookList.books);
for (int i = 0; i < HASH_TABLE_SIZE; i++) {
HashNode* node = lib->hashTable.buckets[i];
while (node != NULL) {
HashNode* temp = node;
node = node->next;
free(temp);
}
}
}
// ========== 主函数测试 ==========
int main() {
Library lib;
initLibrary(&lib);
// 测试:添加几本书
insertBook(&lib, 1001, "深入理解计算机系统", "Randal E. Bryant");
insertBook(&lib, 1002, "算法导论", "Thomas H. Cormen");
insertBook(&lib, 1003, "C程序设计语言", "Brian W. Kernighan");
// 查找测试
searchBookByISBN(&lib, 1002);
searchBookByISBN(&lib, 999); // 不存在
// 遍历测试
traverseBooks(&lib);
// 删除测试
deleteBookByISBN(&lib, 1002);
// 再次遍历
traverseBooks(&lib);
// 再查一次被删的
searchBookByISBN(&lib, 1002);
// 销毁
destroyLibrary(&lib);
return 0;
}
```
---
## 🔍 代码解释说明
| 模块 | 功能说明 |
|------|--------|
| `Book`, `BookList` | 使用动态数组作为主存储,支持高效遍历和缓存友好访问 |
| `HashNode`, `HashTable` | 拉链法实现哈希表,每个桶是一个链表,存储索引 |
| `hash()` | 简单取模哈希函数,适用于整数 ISBN |
| `insertBook()` | 同时写入线性表和哈希表 |
| `searchBookByISBN()` | 利用哈希表 O(1) 平均时间查找 |
| `deleteBookByISBN()` | 删除时需重建哈希表(因索引变动)——这是当前设计的主要代价 |
| `rebuildHashTable()` | 删除/移动后保证哈希表正确性 |
---
## ⚠️ 设计权衡与局限性
1. **为什么用线性表?**
- 遍历快,缓存命中率高;
- 但删除时移动数据会导致索引失效。
2. **删除为何要重建哈希表?**
- 因为我们将“索引”作为哈希表值,一旦删除中间元素,后续索引全部改变。
- 替代方案:
- 使用链表存储书籍(但遍历慢);
- 或在 `Book` 中增加 `valid` 标志位,做逻辑删除(空间换时间);
- 或让哈希表直接存储指针(但在 realloc 时可能失效)。
3. **链地址法优点:**
- 冲突处理自然;
- 不怕聚集;
- 可动态扩展链表长度。
4. **最坏情况效率:**
- 所有键哈希到同一桶 → 查找退化为 O(n)
- 平均情况:O(1),前提是哈希函数良好且负载因子低
---
## ✅ 扩展思考解答
### 1. 如何支持按书名检索?
可以额外建立一个哈希表,以书名为关键字,同样指向线性表索引。但由于书名可能重复,需允许同名书籍或使用链表处理。
> 更优选择:使用平衡树或 Trie 树支持模糊匹配(如“算法”相关书籍)。
### 2. 顺序表 vs 链表作主存储
| 特性 | 顺序表(当前实现) | 链表 |
|------|------------------|-----|
| 查找第i个元素 | O(1) | O(i) |
| 插入末尾 | 分摊 O(1) | O(1) |
| 删除元素 | 移动开销大 | O(1) 给定指针 |
| 空间利用率 | 高(紧凑) | 低(额外指针) |
| 哈希表索引稳定性 | 删除后易失效 | 删除不影响其他节点地址 |
| 推荐场景 | 频繁遍历、读多删少 | 频繁增删 |
改进方向:可用“懒删除 + 定期压缩”减少重建开销。
---