上一章,我們講了hash表
的數據結構,並簡單實現了hash表
的初始化與刪除操作,這一章我們會講解Hash函數
和實現算法,並手動實現一個Hash
函數。
Hash函數
本教程中我們實現的Hash函數
將會實現如下操作:
- 輸入一個字符串,然後返回一個
0
到m
(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函數
主要分爲兩步:
- 將字符串轉爲大整型
- 通過取餘數
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服務)的拒絕服務攻擊。
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。