接口
哈希函数将会实现以下的API:
// hash_table.h
void ht_insert(ht_hash_table* ht, const char* key, const char* value);
char* ht_search(ht_hash_table* ht, const char* key);
void ht_delete(ht_hash_table* h, const char* key);
插入
为了插入一个新的键值对,我们通过索引遍历直至找到一个空的桶为止。然后我们把键值对插入到这个桶中,并且把哈希表的count
属性加一以指明又一个新的键值对加入了。在下一节,我们考虑扩张哈希表的count
属性会变得非常有用。
// hash_table.c
void ht_insert(ht_hash_table* ht, const char* key, const char* value) {
ht_item* item = ht_new_item(key, value);
int index = ht_get_hash(item->key, ht->size, 0);
ht_item* cur_item = ht->items[index];
int i = 1;
while (cur_item != NULL) {
index = ht_get_hash(item->key, ht->size, i);
cur_item = ht->items[index];
i++;
}
ht->items[index] = item;
ht->count++;
}
搜索
搜索跟插入操作相似,但是在每次遍历中,我们会检查当前键值对的关键字跟我们查找的是否相同。如果相同,我们就返回键值对的值。如果遍历过程命中了NULL
桶,我们会返回NULL
以指明没有找到结果。
// hash_table.c
char* ht_search(ht_hash_table* ht, const char* key) {
int index = ht_get_hash(key, ht->size, 0);
ht_item* item = ht->items[index];
int i = 1;
while (item != NULL) {
if (strcmp(item->key, key) == 0) {
return item->value;
}
index = ht_get_hash(key, ht->size, i);
item = ht->items[index];
i++;
}
return NULL;
}
删除
从开放地址哈希表中删除(键值对)会比插入或查找更加复杂。我们希望删除的键值对可能是冲突链的一部分。打断冲突链并将其从哈希表中删除会导致无法查找链条尾端的键值对。为了解决这个问题,我们只是简单的把它标记为已删除。
我们通过替换为一个指向全局哨兵变量(它表示桶中包含已删除的键值对)的指针来把键值对标记为已删除。
// hash_table.c
static ht_item HT_DELETED_ITEM = {NULL, NULL};
void ht_delete(ht_hash_table* ht, const char* key) {
int index = ht_get_hash(key, ht->size, 0);
ht_item* item = ht->items[index];
int i = 1;
while (item != NULL) {
if (item != &HT_DELETED_ITEM) {
if (strcmp(item->key, key) == 0) {
ht_del_item(item);
ht->items[index] = &HT_DELETED_ITEM;
}
}
index = ht_get_hash(key, ht->size, i);
item = ht->items[index];
i++;
}
ht->count--;
}
删除完了,我们把哈希表的count
属性减一。
我们还需要修改ht_insert
函数和ht_search
函数来考虑已被删除的节点。
查找的时候,我们会忽略并跳过已被删除的节点。插入的时候,如果我们命中了一个已被删除的节点,我们可以插入新的节点到已被删除的槽中。
// hash_table.c
void ht_insert(ht_hash_table* ht, const char* key, const char* value) {
// ...
while (cur_item != NULL && cur_item != &HT_DELETED_ITEM) {
// ...
}
// ...
}
char* ht_search(ht_hash_table* ht, const char* key) {
// ...
while (item != NULL) {
if (item != &HT_DELETED_ITEM) {
if (strcmp(item->key, key) == 0) {
return item->value;
}
}
// ...
}
// ...
}
更新
目前,我们的哈希表还不支持更新关键字对应的值。如果我们插入了两个相同关键字的键值对时,关键字会冲突,第二个键值对会被插入到下一个可用的桶中。查找关键字时,原来的关键字总是能被找到,同时我们也无法访问第二个键值对。
我们可以通过修改ht_insert
函数来删除前一个键值对并在原地插入新的键值对来修复这个问题。
// hash_table.c
void ht_insert(ht_hash_table* ht, const char* key, const char* value) {
// ...
while (cur_item != NULL) {
if (cur_item != &HT_DELETED_ITEM) {
if (strcmp(cur_item->key, key) == 0) {
ht_del_item(cur_item);
ht->items[index] = item;
return;
}
}
// ...
}
// ...
}
上一篇:教你从零开始写一个哈希表–哈希冲突
下一篇:教你从零开始写一个哈希表–调整大小