教你從零開始寫一個雜湊表--雜湊函式
阿新 • • 發佈:2018-12-16
在這一節,我們來編寫雜湊函式。
我們選擇的雜湊函式應該具有(以下特性):
- 把字串作為輸入,返回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服務。
上一篇:教你從零開始寫一個雜湊表–雜湊表結構
下一篇:教你從零開始寫一個雜湊表–雜湊衝突