C语言哈希表实现:线性探测法深度解析

C语言哈希表实现:线性探测法深度解析
今日目标
理解哈希表的基本概念和工作原理
掌握线性探测法的冲突解决机制
学会用C语言实现完整的哈希表
了解哈希函数的设计原则
掌握内存管理和性能优化技巧
哈希表概述
哈希表是最高效的数据结构之一,在快速查找、插入和删除操作方面表现出色。本文将从头开始实现一个简单的C语言哈希表,使用开放寻址法中的线性探测来解决冲突。
我们将涵盖:
使用结构体创建键值对
简单的字符串哈希函数
线性探测的冲突解决
插入、搜索和删除操作
完整的演示和解释
数据结构设计
哈希表项结构
  1. #define TABLE_SIZE 100

  2. typedef struct {
  3. char* key; // 键(字符串)
  4. int value; // 值(整数)
  5. int isOccupied; // 槽位状态:0=空,1=占用,-1=已删除
  6. } HashItem;

  7. HashItem table[TABLE_SIZE]; // 全局哈希表
c
状态标记说明
0 (空): 槽位从未被使用过
1 (占用): 槽位当前存储着有效数据
-1 (已删除): 槽位之前被使用过但已被删除(墓碑标记)
哈希函数设计
基本字符串哈希函数
  1. unsigned int hash(const char* key) {
  2. unsigned int hash = 0;
  3. while (*key) {
  4. hash = (hash * 31) + *key++; // 简单哈希逻辑
  5. }
  6. return hash % TABLE_SIZE;
  7. }
c
哈希函数特点
使用31作为乘数(质数,减少冲突)
逐字符计算哈希值
通过模运算确保索引在有效范围内
返回0到TABLE_SIZE-1之间的索引
插入操作实现
线性探测插入逻辑
  1. void insert(const char* key, int value) {
  2. int index = hash(key);
  3. int originalIndex = index;
  4. // 线性探测:处理冲突
  5. while (table[index].isOccupied == 1 && strcmp(table[index].key, key) != 0) {
  6. index = (index + 1) % TABLE_SIZE;
  7. if (index == originalIndex) {
  8. printf("哈希表已满!\n");
  9. return;
  10. }
  11. }
  12. // 插入或更新
  13. if (table[index].isOccupied != 1) {
  14. table[index].key = strdup(key); // 将键复制到堆内存
  15. table[index].value = value;
  16. table[index].isOccupied = 1;
  17. } else {
  18. table[index].value = value; // 如果键已存在则更新值
  19. }
  20. }
c
查找操作实现
搜索函数
  1. int get(const char* key) {
  2. int index = hash(key);
  3. int originalIndex = index;
  4. // 线性探测搜索
  5. while (table[index].isOccupied != 0) {
  6. if (table[index].isOccupied == 1 && strcmp(table[index].key, key) == 0) {
  7. return table[index].value;
  8. }
  9. index = (index + 1) % TABLE_SIZE;
  10. if (index == originalIndex) {
  11. break;
  12. }
  13. }
  14. return -1; // 未找到
  15. }
c
删除操作实现
删除函数
  1. void delete(const char* key) {
  2. int index = hash(key);
  3. int originalIndex = index;
  4. while (table[index].isOccupied != 0) {
  5. if (table[index].isOccupied == 1 && strcmp(table[index].key, key) == 0) {
  6. free(table[index].key); // 释放内存
  7. table[index].key = NULL;
  8. table[index].isOccupied = -1; // 标记为已删除
  9. printf("已删除 '%s'\n", key);
  10. return;
  11. }
  12. index = (index + 1) % TABLE_SIZE;
  13. if (index == originalIndex) {
  14. break;
  15. }
  16. }
  17. printf("未找到键 '%s'。\n", key);
  18. }
c
完整实现示例
主程序演示
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>

  4. #define TABLE_SIZE 100

  5. typedef struct {
  6. char* key;
  7. int value;
  8. int isOccupied;
  9. } HashItem;

  10. HashItem table[TABLE_SIZE];

  11. // 初始化哈希表
  12. void init_table() {
  13. for (int i = 0; i < TABLE_SIZE; i++) {
  14. table[i].key = NULL;
  15. table[i].value = 0;
  16. table[i].isOccupied = 0;
  17. }
  18. }

  19. // 哈希函数
  20. unsigned int hash(const char* key) {
  21. unsigned int hash = 0;
  22. while (*key) {
  23. hash = (hash * 31) + *key++;
  24. }
  25. return hash % TABLE_SIZE;
  26. }

  27. // 插入函数
  28. void insert(const char* key, int value) {
  29. int index = hash(key);
  30. int originalIndex = index;
  31. while (table[index].isOccupied == 1 && strcmp(table[index].key, key) != 0) {
  32. index = (index + 1) % TABLE_SIZE;
  33. if (index == originalIndex) {
  34. printf("哈希表已满!\n");
  35. return;
  36. }
  37. }
  38. if (table[index].isOccupied != 1) {
  39. table[index].key = strdup(key);
  40. table[index].value = value;
  41. table[index].isOccupied = 1;
  42. } else {
  43. table[index].value = value;
  44. }
  45. }

  46. // 查找函数
  47. int get(const char* key) {
  48. int index = hash(key);
  49. int originalIndex = index;
  50. while (table[index].isOccupied != 0) {
  51. if (table[index].isOccupied == 1 && strcmp(table[index].key, key) == 0) {
  52. return table[index].value;
  53. }
  54. index = (index + 1) % TABLE_SIZE;
  55. if (index == originalIndex) {
  56. break;
  57. }
  58. }
  59. return -1;
  60. }

  61. // 删除函数
  62. void delete(const char* key) {
  63. int index = hash(key);
  64. int originalIndex = index;
  65. while (table[index].isOccupied != 0) {
  66. if (table[index].isOccupied == 1 && strcmp(table[index].key, key) == 0) {
  67. free(table[index].key);
  68. table[index].key = NULL;
  69. table[index].isOccupied = -1;
  70. printf("已删除 '%s'\n", key);
  71. return;
  72. }
  73. index = (index + 1) % TABLE_SIZE;
  74. if (index == originalIndex) {
  75. break;
  76. }
  77. }
  78. printf("未找到键 '%s'。\n", key);
  79. }

  80. int main() {
  81. init_table();
  82. // 插入测试数据
  83. insert("apple", 10);
  84. insert("banana", 20);
  85. insert("orange", 30);
  86. insert("grape", 40);
  87. printf("apple: %d\n", get("apple"));
  88. printf("banana: %d\n", get("banana"));
  89. // 删除测试
  90. delete("banana");
  91. printf("删除后 banana: %d\n", get("banana"));
  92. // 重新插入
  93. insert("banana", 50);
  94. printf("重新插入后 banana: %d\n", get("banana"));
  95. return 0;
  96. }
c
运行结果
  1. apple: 10
  2. banana: 20
  3. 已删除 'banana'
  4. 删除后 banana: -1
  5. 重新插入后 banana: 50
性能分析
时间复杂度
平均情况: O(1) - 插入、查找、删除
最坏情况: O(n) - 当哈希表接近满时
最佳情况: O(1) - 无冲突时
空间复杂度
固定大小: O(1) - 预分配TABLE_SIZE个槽位
动态大小: O(n) - 根据实际数据量调整
优化建议
1. 动态扩容
  1. // 动态扩容实现思路
  2. void resize_table() {
  3. // 1. 保存旧表数据
  4. // 2. 创建更大的新表
  5. // 3. 重新哈希所有数据
  6. // 4. 释放旧表内存
  7. }
c
2. 更好的哈希函数
  1. // 改进的哈希函数
  2. unsigned int improved_hash(const char* key) {
  3. unsigned int hash = 5381;
  4. int c;
  5. while ((c = *key++)) {
  6. hash = ((hash << 5) + hash) + c; // hash * 33 + c
  7. }
  8. return hash % TABLE_SIZE;
  9. }
c
3. 双重哈希
  1. // 双重哈希函数
  2. unsigned int hash1(const char* key) {
  3. // 主哈希函数
  4. }

  5. unsigned int hash2(const char* key) {
  6. // 辅助哈希函数
  7. }

  8. // 双重哈希探测
  9. int index = hash1(key);
  10. int step = hash2(key);
  11. while (冲突) {
  12. index = (index + step) % TABLE_SIZE;
  13. }
c
实际应用场景
1. 缓存系统
  1. // 简单的LRU缓存
  2. typedef struct {
  3. char* key;
  4. void* data;
  5. time_t access_time;
  6. int isOccupied;
  7. } CacheItem;
c
2. 符号表
  1. // 编译器符号表
  2. typedef struct {
  3. char* symbol_name;
  4. int symbol_type;
  5. int scope_level;
  6. int isOccupied;
  7. } SymbolTableItem;
c
3. 配置管理
  1. // 配置项存储
  2. typedef struct {
  3. char* config_key;
  4. char* config_value;
  5. int isOccupied;
  6. } ConfigItem;
c
常见问题解决
1. 内存泄漏
  1. // 清理哈希表
  2. void cleanup_table() {
  3. for (int i = 0; i < TABLE_SIZE; i++) {
  4. if (table[i].isOccupied == 1) {
  5. free(table[i].key);
  6. table[i].key = NULL;
  7. }
  8. }
  9. }
c
2. 哈希冲突过多
  1. // 监控冲突率
  2. int collision_count(const char* key) {
  3. int index = hash(key);
  4. int originalIndex = index;
  5. int collisions = 0;
  6. while (table[index].isOccupied == 1 && strcmp(table[index].key, key) != 0) {
  7. index = (index + 1) % TABLE_SIZE;
  8. collisions++;
  9. if (index == originalIndex) break;
  10. }
  11. return collisions;
  12. }
c
今日总结
我们实现了一个简单但完整的C语言哈希表,使用线性探测法解决冲突:
核心要点
数据结构设计: 使用结构体存储键值对和状态信息
哈希函数: 简单有效的字符串哈希算法
冲突解决: 线性探测法处理哈希冲突
内存管理: 正确分配和释放动态内存
墓碑标记: 删除操作的特殊处理
关键特性
快速操作: 平均O(1)时间复杂度的查找、插入、删除
内存效率: 固定大小的数组存储
冲突处理: 线性探测确保数据完整性
易于理解: 清晰的代码结构和注释
扩展方向
动态扩容: 实现自动调整表大小
多种探测: 双重哈希、二次探测
泛型支持: 支持任意数据类型
性能优化: 负载因子监控和自动调整
练习建议
基础练习: 实现不同的哈希函数并比较性能
进阶练习: 添加动态扩容功能
实战练习: 实现LRU缓存或符号表
优化练习: 添加性能监控和统计功能
扩展阅读
掌握哈希表的实现原理,是理解高效数据结构的重要基础!