增加哈希表模块,加速账户ID查找,更新文档

This commit is contained in:
Drunk-Youth
2026-06-24 18:55:35 +08:00
parent bb21a04771
commit 9f381d91a8
6 changed files with 135 additions and 15 deletions
+8 -1
View File
@@ -3,7 +3,7 @@
## 构建 & 运行 ## 构建 & 运行
```sh ```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 ./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` | 输入校验工具 | | `utils.c` | 输入校验工具 |
| `log.c` | 环形队列日志,支持按关键词搜索 | | `log.c` | 环形队列日志,支持按关键词搜索 |
| `graph.c` | 从转账日志建图,分析客户间资金来往关系 | | `graph.c` | 从转账日志建图,分析客户间资金来往关系 |
| `hash.c` | 哈希表,映射账户ID到数组下标,加速查找 |
| `bank.h` | 唯一头文件,声明所有公有函数和类型 | | `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`)。 - 全局变量命名前缀 `g_`(如 `g_iAccCount`, `g_astAccounts`)。
- 无测试;曾有测试文件但已被删除(commit `ce8abe3`)。 - 无测试;曾有测试文件但已被删除(commit `ce8abe3`)。
## 已知陷阱
- **`InitAdminAccount()` 每次启动都调用**,无条件将 `g_astAccounts[0]` 重置为 admin/123456。管理员密码修改在重启后丢失。
- **`GenerateDummyData()`(graph.c)会清空全部日志并修改余额**,仅用于测试演示,非生产操作。
- **`DeleteAccount()` 不递减 `g_iAccCount`**——递减操作由调用方 `bank.c:83` 负责。函数内元素前移循环 `for (; i < g_iAccCount-i; i++)` 有逻辑错误。
+10 -3
View File
@@ -66,6 +66,12 @@
- 最活跃账户排名(度中心性) - 最活跃账户排名(度中心性)
- BFS最短转账路径查找 - BFS最短转账路径查找
9. **哈希表模块(hash.c)**
- 映射账户ID到数组下标,O(1)快速查找
- 开放寻址法,线性探测解决冲突
- 墓碑标记处理删除操作
- 支持从数组重建哈希表
### 数据结构 ### 数据结构
```c ```c
typedef struct strAccount typedef struct strAccount
@@ -81,7 +87,7 @@ typedef struct strAccount
## 使用说明 ## 使用说明
### 编译 ### 编译
```bash ```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 - **密码规则**: 6位数字(100000-999999
- **数据存储**: 二进制文件(bank.dat - **数据存储**: 二进制文件(bank.dat
- **输入验证**: 所有数值输入都包含错误处理 - **输入验证**: 所有数值输入都包含错误处理
- **内存管理**: 使用全局数组存储账户数据 - **内存管理**: 使用全局数组存储账户数据,哈希表加速ID查找
## 文件说明 ## 文件说明
- `bank.c` - 主程序文件 - `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` - 工具函数实现 - `utils.c` - 工具函数实现
- `log.c` - 日志记录和存储(环形队列) - `log.c` - 日志记录和存储(环形队列)
- `graph.c` - 资金关系图分析模块 - `graph.c` - 资金关系图分析模块
- `hash.c` - 哈希表模块,映射账户ID到数组下标
- `bank.h` - 头文件,包含结构体定义和函数声明 - `bank.h` - 头文件,包含结构体定义和函数声明
- `bank.dat` - 数据存储文件(运行时自动生成) - `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
## 扩展建议 ## 扩展建议
- ~~添加交易记录功能~~ - ~~添加交易记录功能~~
- 实现账户排序和搜索 - ~~实现账户快速搜索~~(已通过哈希表实现)
- 增加更多权限级别 - 增加更多权限级别
- 改进用户界面(如使用图形界面) - 改进用户界面(如使用图形界面)
- 添加数据加密功能 - 添加数据加密功能
+3 -6
View File
@@ -62,6 +62,7 @@ void CreateAccount(int quanxian)
g_astAccounts[g_iAccCount] = stNewAcc; g_astAccounts[g_iAccCount] = stNewAcc;
//把记录账户数量的全局变量自增。加过之后自增,所以下标0占用,数量是1个 //把记录账户数量的全局变量自增。加过之后自增,所以下标0占用,数量是1个
HashInsert(stNewAcc.id, g_iAccCount);
g_iAccCount++; g_iAccCount++;
printf("开户成功! 账户ID: %d\n", stNewAcc.id); printf("开户成功! 账户ID: %d\n", stNewAcc.id);
@@ -133,12 +134,7 @@ int LoginAccount(int temp_id) {
*/ */
int FindAccount(int id) int FindAccount(int id)
{ {
for(int i = 0; i < g_iAccCount; i++) return HashSearch(id);
{
if(g_astAccounts[i].id == id) return i;
}
return -1;
} }
//删除账户 //删除账户
void DeleteAccount(int id) { void DeleteAccount(int id) {
@@ -151,6 +147,7 @@ void DeleteAccount(int id) {
printf("正在删除一下账户..."); printf("正在删除一下账户...");
printf("%-8d %-30s %-10.2f %-3d\n", printf("%-8d %-30s %-10.2f %-3d\n",
g_astAccounts[i].id, g_astAccounts[i].name, g_astAccounts[i].balance,g_astAccounts[i].quanxian); g_astAccounts[i].id, g_astAccounts[i].name, g_astAccounts[i].balance,g_astAccounts[i].quanxian);
HashDelete(id);
for (; i < g_iAccCount-i; i++) for (; i < g_iAccCount-i; i++)
{ {
g_astAccounts[i]=g_astAccounts[i+1]; g_astAccounts[i]=g_astAccounts[i+1];
+2
View File
@@ -19,6 +19,7 @@ int main(void)
printf("=== 简易银行账户管理系统 ===\n"); printf("=== 简易银行账户管理系统 ===\n");
printf("欢迎使用! 系统已加载%d个账户\n", g_iAccCount); printf("欢迎使用! 系统已加载%d个账户\n", g_iAccCount);
InitAdminAccount();//初始化管理员账号 InitAdminAccount();//初始化管理员账号
RebuildHashTable();
InitLogQueue(); InitLogQueue();
int iChoice=-1; //选择标志 int iChoice=-1; //选择标志
int pChoice=-1; int pChoice=-1;
@@ -81,6 +82,7 @@ int main(void)
scanf("%d", &d_id); scanf("%d", &d_id);
DeleteAccount(d_id); DeleteAccount(d_id);
g_iAccCount--; g_iAccCount--;
RebuildHashTable();
break; break;
case 6: case 6:
CreateAccount(1); CreateAccount(1);
+16
View File
@@ -3,6 +3,7 @@
#define MAX_LOGS 100 // 日志队列最大容量,防止无限增长 #define MAX_LOGS 100 // 日志队列最大容量,防止无限增长
#define MAX_ACCOUNTS 50 //系统规格为50个账户 #define MAX_ACCOUNTS 50 //系统规格为50个账户
#define NAME_LEN 31 //用户名字最长30字符,因此需要多1个字符串结束符 #define NAME_LEN 31 //用户名字最长30字符,因此需要多1个字符串结束符
#define HASH_SIZE 101 //哈希表大小,取质数以减少冲突
// 账户信息,每个账户id对应了名字、账户余额等信息,是不是用数据结构定义比较合适? // 账户信息,每个账户id对应了名字、账户余额等信息,是不是用数据结构定义比较合适?
typedef struct strAccount typedef struct strAccount
@@ -16,6 +17,21 @@ typedef struct strAccount
// 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中声明 // 函数声明,main函数中调用的各模块函数需要在程序公用的.h中声明
// 公开出来被其他模块调用的函数,也应该在公用的.h中声明 // 公开出来被其他模块调用的函数,也应该在公用的.h中声明
+91
View File
@@ -0,0 +1,91 @@
#include <stdio.h>
#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);
}
}
}