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

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

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

#define TABLE_SIZE 100

typedef struct {
char* key;
int value;
int isOccupied;
} HashItem;

HashItem table[TABLE_SIZE];

// 初始化哈希表
void init_table() {
for (int i = 0; i < TABLE_SIZE; i++) {
table[i].key = NULL;
table[i].value = 0;
table[i].isOccupied = 0;
}
}

// 哈希函数
unsigned int hash(const char* key) {
unsigned int hash = 0;
while (*key) {
hash = (hash * 31) + *key++;
}
return hash % TABLE_SIZE;
}

// 插入函数
void insert(const char* key, int value) {
int index = hash(key);
int originalIndex = index;
while (table[index].isOccupied == 1 && strcmp(table[index].key, key) != 0) {
index = (index + 1) % TABLE_SIZE;
if (index == originalIndex) {
printf("哈希表已满!\n");
return;
}
}
if (table[index].isOccupied != 1) {
table[index].key = strdup(key);
table[index].value = value;
table[index].isOccupied = 1;
} else {
table[index].value = value;
}
}

// 查找函数
int get(const char* key) {
int index = hash(key);
int originalIndex = index;
while (table[index].isOccupied != 0) {
if (table[index].isOccupied == 1 && strcmp(table[index].key, key) == 0) {
return table[index].value;
}
index = (index + 1) % TABLE_SIZE;
if (index == originalIndex) {
break;
}
}
return -1;
}

// 删除函数
void delete(const char* key) {
int index = hash(key);
int originalIndex = index;
while (table[index].isOccupied != 0) {
if (table[index].isOccupied == 1 && strcmp(table[index].key, key) == 0) {
free(table[index].key);
table[index].key = NULL;
table[index].isOccupied = -1;
printf("已删除 '%s'\n", key);
return;
}
index = (index + 1) % TABLE_SIZE;
if (index == originalIndex) {
break;
}
}
printf("未找到键 '%s'。\n", key);
}

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

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

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