哈希表(Hash Table)是一种高效的数据结构,用于存储键值对(Key - Value Pairs),并支持快速的插入、查找和删除操作。其核心思想是通过哈希函数将键映射到一个固定大小的数组中,从而实现接近常数时间复杂度的操作(理想情况下为O(1))。
一、哈希表的核心组成部分
(一)哈希函数(Hash Function)
- 作用
- 将任意大小的键(如整数、字符串、结构体等)映射到一个固定范围的整数(通常是数组的索引)。
- 要求
- 一致性:相同的键必须始终映射到相同的索引。
- 均匀性:哈希函数应尽可能均匀地将键分布到数组中,以减少冲突。
- 示例
- 对于整数键:hash(key)=key % table_size。
- 对于字符串键:可以将每个字符的ASCII码相加,然后对表大小取模。
(二)数组(哈希表)
- 哈希表的核心是一个固定大小的数组,数组的每个位置称为一个桶(Bucket)。
- 每个桶可以存储一个键值对,或者一个链表(用于解决冲突)。
(三)冲突解决机制
- 链地址法(Chaining)
- 每个桶存储一个链表,冲突的键值对会被添加到链表中。
- 查找时,先通过哈希函数找到桶,然后在链表中遍历查找。
- 开放地址法(Open Addressing)
- 当发生冲突时,通过某种规则(如线性探测、二次探测)寻找下一个空闲的位置。
- 查找时,按照相同的规则依次检查桶,直到找到目标键或空桶。
二、哈希表的工作原理
(一)插入操作
- 计算键的哈希值:index = hash(key)。
- 检查哈希表中的index位置:
- 如果为空,直接插入键值对。
- 如果不为空(发生冲突),根据冲突解决机制处理(如链地址法或开放地址法)。
(二)查找操作
- 计算键的哈希值:index = hash(key)。
- 检查哈希表中的index位置:
- 如果该位置的键与目标键匹配,返回对应的值。
- 如果不匹配,根据冲突解决机制继续查找(如在链表中遍历或探测下一个位置)。
(三)删除操作
- 计算键的哈希值:index = hash(key)。
- 检查哈希表中的index位置:
- 如果该位置的键与目标键匹配,删除键值对。
- 如果不匹配,根据冲突解决机制继续查找并删除。
三、哈希表的优缺点
(一)优点
- 高效的操作:在理想情况下,插入、查找和删除的时间复杂度为O(1)。
- 灵活性:可以存储任意类型的键值对。
(二)缺点
- 冲突问题:哈希冲突可能导致性能下降,尤其是在哈希函数设计不佳或数据分布不均匀时。
- 空间浪费:哈希表需要预先分配固定大小的数组,可能导致空间浪费。
- 动态扩展:当数据量增加时,哈希表可能需要重新哈希(Rehashing)以扩展容量,这会带来额外的开销。
四、哈希表的实现示例(C语言)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TABLE_SIZE 100 // 哈希表的大小
// 定义键值对结构体
typedef struct {
char* key; // 键(字符串)
int value; // 值(整数)
} KeyValuePair;
// 定义链表节点
typedef struct Node {
KeyValuePair data; // 键值对
struct Node* next; // 指向下一个节点
} Node;
// 定义哈希表
typedef struct {
Node* buckets[TABLE_SIZE]; // 桶数组
} HashTable;
// 哈希函数:将字符串键映射到整数
unsigned int hash(const char* key) {
unsigned int hash_value = 0;
for (int i = 0; key[i]!= '\0'; i++) {
hash_value = (hash_value * 31+key[i]) % TABLE_SIZE;
}
return hash_value;
}
// 创建新节点
Node* create_node(const char* key, int value) {
Node* node = (Node*)malloc(sizeof(Node));
node->data.key = strdup(key); // 复制字符串键
node->data.value = value;
node->next = NULL;
return node;
}
// 插入键值对
void insert(HashTable* table, const char* key, int value) {
unsigned int index = hash(key); // 计算哈希值
Node* node = create_node(key, value); // 创建新节点
// 插入到链表头部
node->next = table->buckets[index];
table->buckets[index] = node;
}
// 查找键对应的值
int find(HashTable* table, const char* key) {
unsigned int index = hash(key); // 计算哈希值
Node* current = table->buckets[index];
// 遍历链表
while (current!= NULL) {
if (strcmp(current->data.key, key) == 0) {
return current->data.value; // 找到键,返回值
}
current = current->next;
}
return -1; // 未找到键
}
// 删除键值对
void delete(HashTable* table, const char* key) {
unsigned int index = hash(key); // 计算哈希值
Node* current = table->buckets[index];
Node* prev = NULL;
// 遍历链表
while (current!= NULL) {
if (strcmp(current->data.key, key) == 0) {
if (prev == NULL) {
// 删除链表头部
table->buckets[index] = current->next;
} else {
// 删除链表中间或尾部
prev->next = current->next;
}
free(current->data.key); // 释放键的内存
free(current); // 释放节点的内存
return;
}
prev = current;
current = current->next;
}
}
// 主函数
int main() {
HashTable table = {0}; // 初始化哈希表
// 插入键值对
insert(&table, "apple", 10);
insert(&table, "banana", 20);
insert(&table, "orange", 30);
// 查找键值对
printf("apple: %d\n", find(&table, "apple")); // 输出: apple: 10
printf("banana: %d\n", find(&table, "banana")); // 输出: banana: 20
printf("grape: %d\n", find(&table, "grape")); // 输出: grape: -1
// 删除键值对
delete(&table, "banana");
printf("banana: %d\n", find(&table, "banana")); // 输出: banana: -1
return 0;
}
五、总结
哈希表是一种高效的数据结构,适用于需要快速查找、插入和删除的场景。其核心是哈希函数和冲突解决机制。通过合理设计哈希函数和选择合适的冲突解决方法,可以充分发挥哈希表的性能优势。