散列表將鍵通過哈希函數映射為數組下標,衝突鍵掛在該下標的鏈表上(開鏈法)。核心優勢:
O(1) 定位桶 + O(链长) 遍歷O(1)O(n + 桶數)| 場景 | 推薦哈希函數 | 說明 |
|---|---|---|
| 整數鍵 | H(x) = x % P | P 為接近桶數的質數,減少聚集 |
| 字符串 | BKDR / FNV / DJB2 | 經典字符串哈希,位運算混合字符碼 |
| 自定義結構 | 組合關鍵字段哈希 | hash = (hash * 31) ^ field_hash |
| 大整數/任意精度 | 取模大質數 | 或用雙重哈希(兩個質數取模) |
黃金法則:好的哈希函數讓桶分佈盡可能均勻,負載因子 α = n/桶數 控制在 0.7~1.0,必要時 rehash 擴容。
template<class T, class Hash = std::hash<T>, class Eq = std::equal_to<T>>
class HashTable {
struct Node {
T key; Node* next;
Node(const T& k, Node* n) : key(k), next(n) {}
};
vector<Node*> buckets;
Hash hasher; Eq eq;
size_t sz = 0;
static constexpr float MAX_LOAD = 0.75;
size_t idx(const T& k) const { return hasher(k) % buckets.size(); }
void rehash() {
vector<Node*> old = buckets;
buckets.assign(buckets.size() * 2, nullptr);
sz = 0;
for (auto head : old)
for (Node* p = head; p; p = p->next)
insert(p->key);
}
public:
HashTable(size_t n = 101) : buckets(n, nullptr) {} // 質數起始
bool insert(const T& key) {
if (find(key)) return false;
size_t i = idx(key);
buckets[i] = new Node(key, buckets[i]);
if (++sz > buckets.size() * MAX_LOAD) rehash();
return true;
}
bool find(const T& key) const {
for (Node* p = buckets[idx(key)]; p; p = p->next)
if (eq(p->key, key)) return true;
return false;
}
bool erase(const T& key) {
size_t i = idx(key);
Node **pp = &buckets[i];
while (*pp) {
if (eq((*pp)->key, key)) {
Node* del = *pp;
*pp = del->next;
delete del;
--sz;
return true;
}
pp = &(*pp)->next;
}
return false;
}
~HashTable() {
for (auto head : buckets)
while (head) { Node* t = head; head = head->next; delete t; }
}
};
除非極度追求緩存局部性且鍵值極小(如 int->int),否則開鏈法是工程首選。
O(n+m) 多模式匹配哈希表是時空權衡的經典範例:用額外空間(桶數組)換取極速查找。掌握哈希函數選擇、衝突解決策略、動態擴容機制、負載因子控制四大核心,即可靈活應對從嵌入式去重到分佈式路由的各種場景。記住:開鏈法通用、質數取模基礎、rehash 必備、閉散列慎用。