1. 程式人生 > >教你從零開始寫一個雜湊表--雜湊函式

教你從零開始寫一個雜湊表--雜湊函式

  
在這一節,我們來編寫雜湊函式。
我們選擇的雜湊函式應該具有(以下特性):

  • 把字串作為輸入,返回0到m(我們設計的桶陣列的長度)的數字;
  • 對於一組平均的輸入返回分佈比較均勻的桶索引。如果我們的雜湊函式不是均勻分佈的,它可將會把較多的一些鍵值對放在某幾個桶中。這將會導致更高的衝突概率。衝突降低了雜湊表的效率。

我們使用的是常見的字串雜湊函式,虛擬碼的表達如下:

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

這個雜湊函式有兩個步驟:

  • 把字串轉換成一個較大的整數;
  • 通過取這個數的餘數跟m取模來將整數的大小減小到固定的範圍。
    變數a得是一個比字元表大小要大的素數。我們雜湊的ASCII字串,有128個字元。因此我們應該選擇一個比128要大的素數。
    char_code是一個返回一個表示字元的對應整數值的函式。我們用的是ASCII字碼表。

我們來試一下這個雜湊函式吧:

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(n)) 複雜度的時間,而不是(O(1))。這可能會被用作針對使用雜湊表的系統進行的拒絕服務攻擊,比如DNS和某些web服務。

上一篇:教你從零開始寫一個雜湊表–雜湊表結構
下一篇:教你從零開始寫一個雜湊表–雜湊衝突