1. 程式人生 > >[譯]C語言實現一個簡易的Hash table(3)

[譯]C語言實現一個簡易的Hash table(3)

上一章,我們講了hash表的資料結構,並簡單實現了hash表的初始化與刪除操作,這一章我們會講解Hash函式和實現演算法,並手動實現一個Hash函式。

Hash函式

本教程中我們實現的Hash函式將會實現如下操作:

  • 輸入一個字串,然後返回一個0m(Hash表的大小)的數字
  • 為一組平常的輸入返回均勻的bucket索引。如果Hash函式不是均勻分佈的,就會將多個記錄插入到相同的bucket中,這就回提高衝突的機率,而這個衝突就會影響到我們的Hash表的效率。

Hash演算法

我們將會設計一個普通的字串Hash函式,在虛擬碼中表示如下:

function hash(string, a, num_buckets):
    hash = 0
    string_len = length(string)
    for i = 0, 1, ..., string_len:
        hash += (a ** (string_len - (i+1))) * char_code(string[I])
    hash = hash % num_buckets
    return hash

這個Hash函式主要分為兩步:

  1. 將字串轉為大整型
  2. 通過取餘數mod m將整數的大小減小到固定範圍

變數a是一個素數,並且要大於英文字母,我們正在雜湊ASCII字串,其字母大小為128,因此我們應該選擇大於此的素數。

char_code這個函式會返回字母對應的整數,使用的是ASCII中的字母。

如下使用這個Hash函式

hash("cat", 151, 53)

// 函式拆解
hash = (151**2 * 99 + 151**1 * 97 + 151**0 * 116) % 53
hash = (2257299 + 14647 + 116) % 53
hash = (2272062) % 53
hash = 5

如果改變a我們會得到不同的結果:

hash("cat", 163, 53) = 3

程式碼實現

// hash_table.c
static int ht_hash(const char* s, const int a, const int m) {
    long hash = 0;
    const int len_s = strlen(s);
    for (int i = 0; i < len_s; i++) {
        hash += (long)pow(a, len_s - (i+1)) * s[i];
        hash = hash % m;
    }
    return (int)hash;
}

什麼是衝突?

理想中的雜湊函式返回的結果都是均勻分佈的,但是,對於任意一個雜湊函式,總會有一些輸入經過雜湊後,得到相同的值。如果要找到這組輸入,我們就需要測試大量的輸入資料。

因為上面提到的有不好的輸入存在,意味著所有輸入都沒有完美的雜湊函式。所以在設計雜湊函式時,針對預期輸入,我們的雜湊函式需要表現最好。

不好的輸入也存在安全問題,如果某個惡意使用者向雜湊表提供了一組衝突金鑰,那麼搜尋這些金鑰將比正常情況(O(1))花費更長時間(O(n))。這可以用作針對以雜湊表為基礎的系統(例如DNS和某些Web服務)的拒絕服務攻擊。

上一章:Hash table資料結構
下一章:衝突處理


原文地址:https://github.com/jamesroutley/write-a-hash-table/tree/master/03-hashing