From 9f381d91a8fcd17ddadce6af3c375d9cfd681078 Mon Sep 17 00:00:00 2001 From: Drunk-Youth Date: Wed, 24 Jun 2026 18:55:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=93=88=E5=B8=8C=E8=A1=A8?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=8A=A0=E9=80=9F=E8=B4=A6=E6=88=B7?= =?UTF-8?q?ID=E6=9F=A5=E6=89=BE=EF=BC=8C=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 9 +++++- README.md | 23 +++++++++----- account.c | 9 ++---- bank.c | 2 ++ bank.h | 16 ++++++++++ hash.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 hash.c diff --git a/AGENTS.md b/AGENTS.md index c93ce62..3eaae83 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ ## 构建 & 运行 ```sh -gcc bank.c account.c transaction.c file_io.c utils.c log.c graph.c -o bank_system +gcc bank.c account.c transaction.c file_io.c utils.c log.c graph.c hash.c -o bank_system ./bank_system ``` @@ -22,6 +22,7 @@ gcc bank.c account.c transaction.c file_io.c utils.c log.c graph.c -o bank_syste | `utils.c` | 输入校验工具 | | `log.c` | 环形队列日志,支持按关键词搜索 | | `graph.c` | 从转账日志建图,分析客户间资金来往关系 | +| `hash.c` | 哈希表,映射账户ID到数组下标,加速查找 | | `bank.h` | 唯一头文件,声明所有公有函数和类型 | ## 重要约束 @@ -38,3 +39,9 @@ gcc bank.c account.c transaction.c file_io.c utils.c log.c graph.c -o bank_syste - 中文注释和中文菜单字符串。 - 全局变量命名前缀 `g_`(如 `g_iAccCount`, `g_astAccounts`)。 - 无测试;曾有测试文件但已被删除(commit `ce8abe3`)。 + +## 已知陷阱 + +- **`InitAdminAccount()` 每次启动都调用**,无条件将 `g_astAccounts[0]` 重置为 admin/123456。管理员密码修改在重启后丢失。 +- **`GenerateDummyData()`(graph.c)会清空全部日志并修改余额**,仅用于测试演示,非生产操作。 +- **`DeleteAccount()` 不递减 `g_iAccCount`**——递减操作由调用方 `bank.c:83` 负责。函数内元素前移循环 `for (; i < g_iAccCount-i; i++)` 有逻辑错误。 diff --git a/README.md b/README.md index 9f7e089..8524a1a 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,17 @@ - 日志记录和存储(环形队列) 8. **资金关系图分析(graph.c)** - - 从转账日志构建邻接矩阵 - - 显示全部转账关系图 - - 查看指定账户关联关系 - - 最活跃账户排名(度中心性) - - BFS最短转账路径查找 + - 从转账日志构建邻接矩阵 + - 显示全部转账关系图 + - 查看指定账户关联关系 + - 最活跃账户排名(度中心性) + - BFS最短转账路径查找 + +9. **哈希表模块(hash.c)** + - 映射账户ID到数组下标,O(1)快速查找 + - 开放寻址法,线性探测解决冲突 + - 墓碑标记处理删除操作 + - 支持从数组重建哈希表 ### 数据结构 ```c @@ -81,7 +87,7 @@ typedef struct strAccount ## 使用说明 ### 编译 ```bash -gcc bank.c account.c transaction.c file_io.c utils.c log.c graph.c -o bank_system +gcc bank.c account.c transaction.c file_io.c utils.c log.c graph.c hash.c -o bank_system ``` ### 运行 @@ -127,7 +133,7 @@ gcc bank.c account.c transaction.c file_io.c utils.c log.c graph.c -o bank_syste - **密码规则**: 6位数字(100000-999999) - **数据存储**: 二进制文件(bank.dat) - **输入验证**: 所有数值输入都包含错误处理 -- **内存管理**: 使用全局数组存储账户数据 +- **内存管理**: 使用全局数组存储账户数据,哈希表加速ID查找 ## 文件说明 - `bank.c` - 主程序文件 @@ -137,6 +143,7 @@ gcc bank.c account.c transaction.c file_io.c utils.c log.c graph.c -o bank_syste - `utils.c` - 工具函数实现 - `log.c` - 日志记录和存储(环形队列) - `graph.c` - 资金关系图分析模块 +- `hash.c` - 哈希表模块,映射账户ID到数组下标 - `bank.h` - 头文件,包含结构体定义和函数声明 - `bank.dat` - 数据存储文件(运行时自动生成) @@ -149,7 +156,7 @@ gcc bank.c account.c transaction.c file_io.c utils.c log.c graph.c -o bank_syste ## 扩展建议 - ~~添加交易记录功能~~ -- 实现账户排序和搜索 +- ~~实现账户快速搜索~~(已通过哈希表实现) - 增加更多权限级别 - 改进用户界面(如使用图形界面) - 添加数据加密功能 diff --git a/account.c b/account.c index eb85def..d1dc22d 100644 --- a/account.c +++ b/account.c @@ -62,6 +62,7 @@ void CreateAccount(int quanxian) g_astAccounts[g_iAccCount] = stNewAcc; //把记录账户数量的全局变量自增。加过之后自增,所以下标0占用,数量是1个 + HashInsert(stNewAcc.id, g_iAccCount); g_iAccCount++; printf("开户成功! 账户ID: %d\n", stNewAcc.id); @@ -133,12 +134,7 @@ int LoginAccount(int temp_id) { */ int FindAccount(int id) { - for(int i = 0; i < g_iAccCount; i++) - { - if(g_astAccounts[i].id == id) return i; - } - - return -1; + return HashSearch(id); } //删除账户 void DeleteAccount(int id) { @@ -151,6 +147,7 @@ void DeleteAccount(int id) { printf("正在删除一下账户..."); printf("%-8d %-30s %-10.2f %-3d\n", g_astAccounts[i].id, g_astAccounts[i].name, g_astAccounts[i].balance,g_astAccounts[i].quanxian); + HashDelete(id); for (; i < g_iAccCount-i; i++) { g_astAccounts[i]=g_astAccounts[i+1]; diff --git a/bank.c b/bank.c index bd146df..133289f 100644 --- a/bank.c +++ b/bank.c @@ -19,6 +19,7 @@ int main(void) printf("=== 简易银行账户管理系统 ===\n"); printf("欢迎使用! 系统已加载%d个账户\n", g_iAccCount); InitAdminAccount();//初始化管理员账号 + RebuildHashTable(); InitLogQueue(); int iChoice=-1; //选择标志 int pChoice=-1; @@ -81,6 +82,7 @@ int main(void) scanf("%d", &d_id); DeleteAccount(d_id); g_iAccCount--; + RebuildHashTable(); break; case 6: CreateAccount(1); diff --git a/bank.h b/bank.h index dd92b4f..54f4985 100644 --- a/bank.h +++ b/bank.h @@ -3,6 +3,7 @@ #define MAX_LOGS 100 // 日志队列最大容量,防止无限增长 #define MAX_ACCOUNTS 50 //系统规格为50个账户 #define NAME_LEN 31 //用户名字最长30字符,因此需要多1个字符串结束符 +#define HASH_SIZE 101 //哈希表大小,取质数以减少冲突 // 账户信息,每个账户id对应了名字、账户余额等信息,是不是用数据结构定义比较合适? typedef struct strAccount @@ -15,6 +16,21 @@ typedef struct strAccount } STACCOUNT; // strAccount STACCOUNT; + +// 哈希表条目,映射账户ID到g_astAccounts数组下标 +typedef struct { + int id; // 账户ID,-1表示空槽位 + int index; // g_astAccounts中的下标 +} HashEntry; + +extern HashEntry g_hashTable[HASH_SIZE]; + +// 哈希表操作 +void InitHashTable(); +int HashInsert(int id, int index); +int HashSearch(int id); +void HashDelete(int id); +void RebuildHashTable(); // 函数声明,main函数中调用的各模块函数需要在程序公用的.h中声明 // 公开出来被其他模块调用的函数,也应该在公用的.h中声明 diff --git a/hash.c b/hash.c new file mode 100644 index 0000000..953a4f1 --- /dev/null +++ b/hash.c @@ -0,0 +1,91 @@ +#include +#include "bank.h" + +extern STACCOUNT g_astAccounts[]; +extern int g_iAccCount; + +HashEntry g_hashTable[HASH_SIZE]; + +static int HashFunc(int id) +{ + return id % HASH_SIZE; +} + +void InitHashTable() +{ + int i; + for (i = 0; i < HASH_SIZE; i++) { + g_hashTable[i].id = -1; + g_hashTable[i].index = -1; + } +} + +int HashInsert(int id, int index) +{ + int pos = HashFunc(id); + int first_tombstone = -1; + int i; + for (i = 0; i < HASH_SIZE; i++) { + int probe = (pos + i) % HASH_SIZE; + if (g_hashTable[probe].id == id) { + g_hashTable[probe].index = index; + return 0; + } + if (g_hashTable[probe].id == -1) { + if (first_tombstone != -1) { + probe = first_tombstone; + } + g_hashTable[probe].id = id; + g_hashTable[probe].index = index; + return 0; + } + if (g_hashTable[probe].id == -2 && first_tombstone == -1) { + first_tombstone = probe; + } + } + return -1; +} + +int HashSearch(int id) +{ + int pos = HashFunc(id); + int i; + for (i = 0; i < HASH_SIZE; i++) { + int probe = (pos + i) % HASH_SIZE; + if (g_hashTable[probe].id == id) { + return g_hashTable[probe].index; + } + if (g_hashTable[probe].id == -1) { + return -1; + } + } + return -1; +} + +void HashDelete(int id) +{ + int pos = HashFunc(id); + int i; + for (i = 0; i < HASH_SIZE; i++) { + int probe = (pos + i) % HASH_SIZE; + if (g_hashTable[probe].id == -1) { + return; + } + if (g_hashTable[probe].id == id) { + g_hashTable[probe].id = -2; + g_hashTable[probe].index = -1; + return; + } + } +} + +void RebuildHashTable() +{ + int i; + InitHashTable(); + for (i = 0; i < g_iAccCount; i++) { + if (g_astAccounts[i].id != 0) { + HashInsert(g_astAccounts[i].id, i); + } + } +}