教你從零開始寫一個哈希表--接口

接口

哈希函數將會實現以下的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;
            }
        }
        // ...
    } 
    // ...
}

上一篇:教你從零開始寫一個哈希表–哈希衝突
下一篇:教你從零開始寫一個哈希表–調整大小

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章