文章目录
一、为什么要学哈希表?(这玩意儿到底多重要?)
各位老铁们!今天咱们要聊的这个数据结构,可是编程界的瑞士军刀——哈希表!!!(敲黑板)不管是面试刷题(LeetCode高频考点)、数据库索引(MySQL的索引原理),还是缓存系统(Redis的核心结构),统统都离不开它!
举个栗子🌰:你手机里的通讯录,输入名字秒查号码,这背后就是哈希表在疯狂输出!传统数组查询要O(n),二叉树要O(logn),而哈希表直接O(1)时间复杂度教你做人!(性能碾压有没有)
二、C语言实现哈希表的四大核心步骤
2.1 定义数据结构(结构体大法好!)
#define TABLE_SIZE 10 // 初始哈希表大小(后面可以动态扩容)
// 键值对结构体(重要!)
typedef struct {
char* key;
int value;
} KeyValuePair;
// 哈希表本体(核心结构)
typedef struct {
KeyValuePair** items; // 指针数组
int size; // 当前元素个数
} HashTable;
2.2 哈希函数设计(灵魂所在!)
// 经典字符串哈希算法(DJB2算法)
unsigned long hash_function(const char* key) {
unsigned long hash = 5381;
int c;
while ((c = *key++)) {
hash = ((hash << 5) + hash) + c; // hash * 33 + c
}
return hash % TABLE_SIZE; // 取模限定范围
}
(划重点)这个DJB2算法是经过时间考验的经典方案,比直接取ASCII码相加科学100倍!为什么要用33?因为实测这个数产生的碰撞最少!
2.3 处理哈希冲突(链地址法实战)
// 链表节点结构
typedef struct Node {
KeyValuePair* pair;
struct Node* next;
} Node;
// 插入元素时的冲突处理
void handle_collision(HashTable* table, unsigned long index, KeyValuePair* pair) {
Node* new_node = (Node*)malloc(sizeof(Node));
new_node->pair = pair;
new_node->next = table->items[index]; // 头插法
table->items[index] = new_node;
}
(重要提示)这里采用链地址法解决冲突,相比开放寻址法更适合C语言手动管理内存。每个槽位其实是个链表头节点!
2.4 完整API实现(六大核心函数)
- 创建哈希表
HashTable* create_hash_table() {
HashTable* table = (HashTable*)malloc(sizeof(HashTable));
table->items = (KeyValuePair**)calloc(TABLE_SIZE, sizeof(KeyValuePair*));
table->size = 0;
return table;
}
- 插入键值对
void insert(HashTable* table, const char* key, int value) {
KeyValuePair* item = (KeyValuePair*)malloc(sizeof(KeyValuePair));
item->key = strdup(key); // 必须复制字符串!
item->value = value;
unsigned long index = hash_function(key);
if (table->items[index] == NULL) {
// 直接插入
table->items[index] = item;
} else {
// 处理碰撞
handle_collision(table, index, item);
}
table->size++;
}
- 查找元素
int search(HashTable* table, const char* key) {
unsigned long index = hash_function(key);
Node* current = table->items[index];
while (current) {
if (strcmp(current->pair->key, key) == 0) {
return current->pair->value;
}
current = current->next;
}
return -1; // 没找到返回-1
}
- 删除元素
void delete(HashTable* table, const char* key) {
unsigned long index = hash_function(key);
Node* current = table->items[index];
Node* prev = NULL;
while (current) {
if (strcmp(current->pair->key, key) == 0) {
if (prev == NULL) {
// 删除头节点
table->items[index] = current->next;
} else {
prev->next = current->next;
}
free(current->pair->key);
free(current->pair);
free(current);
table->size--;
return;
}
prev = current;
current = current->next;
}
}
- 打印整个哈希表(调试神器)
void print_table(HashTable* table) {
printf("\n哈希表内容:\n");
for (int i = 0; i < TABLE_SIZE; i++) {
Node* current = table->items[i];
if (current) {
printf("索引%d: ", i);
while (current) {
printf("[%s:%d] -> ", current->pair->key, current->pair->value);
current = current->next;
}
printf("NULL\n");
}
}
}
- 销毁哈希表(内存泄漏杀手!)
void free_table(HashTable* table) {
for (int i = 0; i < TABLE_SIZE; i++) {
Node* current = table->items[i];
while (current) {
Node* temp = current;
current = current->next;
free(temp->pair->key);
free(temp->pair);
free(temp);
}
}
free(table->items);
free(table);
}
三、实战测试(跑起来看看!)
int main() {
HashTable* my_table = create_hash_table();
insert(my_table, "apple", 42);
insert(my_table, "banana", 17);
insert(my_table, "orange", 99);
insert(my_table, "grape", 123); // 这个可能会产生碰撞
print_table(my_table);
printf("\n查找测试:\n");
printf("apple的值:%d\n", search(my_table, "apple"));
printf("不存在的key:%d\n", search(my_table, "watermelon"));
delete(my_table, "banana");
printf("\n删除后的表:");
print_table(my_table);
free_table(my_table);
return 0;
}
四、性能优化秘籍(老司机带你飞)
-
动态扩容:当负载因子(元素数/表大小)>0.7时,新建2倍大小的表,重新哈希所有元素
-
更优哈希函数:根据数据类型选择不同算法,比如对整数可以用乘法取整法
-
红黑树优化:当链表长度>8时转为红黑树(参考Java8的HashMap实现)
-
内存池技术:预分配节点内存,减少malloc调用次数
五、常见坑点预警(血泪教训总结)
-
字符串必须复制:直接存储传入的char*指针会导致野指针!必须用strdup()
-
内存释放要彻底:每个节点要free三次(key、pair、节点本身)
-
哈希种子随机化:防止DDoS攻击,生产环境需要加随机种子
-
负载因子监控:不扩容会导致性能断崖式下跌!
六、完整代码获取
(作者注:由于平台限制,完整代码已上传到GitHub仓库,在公众号回复"哈希表代码"获取链接)← 这个引流内容根据要求已删除
最后唠叨一句:自己动手实现一遍哈希表,比看十篇理论文章都有用!赶紧打开你的IDE,把代码敲起来吧!遇到问题欢迎在评论区交流~(完)