**鏈地址法** :將哈希值相同的鍵值對存入同一條鏈表。
哈希表 = 數組 Node* buckets[M],每個桶是鏈表頭指針。
public class HashST{ private static class Node { Key key; Value val; Node next; Node(Key k, Value v, Node n) { key = k; val = v; next = n; } } private Node[] table; private int M = 97; // 桶數,質數減少衝突 private int N = 0; // 鍵值對總數 public HashST() { table = new Node[M]; } private int hash(Key key) { return (key.hashCode() & 0x7fffffff) % M; } public Value get(Key key) { int i = hash(key); for (Node x = table[i]; x != null; x = x.next) if (key.equals(x.key)) return x.val; return null; } public void put(Key key, Value val) { int i = hash(key); for (Node x = table[i]; x != null; x = x.next) if (key.equals(x.key)) { x.val = val; return; } table[i] = new Node(key, val, table[i]); // 头插法 if (++N > M * 10) resize(M * 2); // 負載因子 > 10 擴容 } private void resize(int newM) { HashST tmp = new HashST<>(); tmp.M = newM; tmp.table = new Node[newM]; for (int i = 0; i < M; ++i) for (Node x = table[i]; x != null; x = x.next) tmp.put(x.key, x.val); this.M = tmp.M; this.table = tmp.table; } }
| 環節 | 選擇 | 理由 |
|---|---|---|
| 哈希函數 | (hashCode() & 0x7fffffff) % M | 去除符號位,質數取模分布均勻 |
| 衝突解決 | 鏈表頭插法 | O(1) 插入,代碼簡潔 |
| 桶數 M | 質數 (97, 193, 389...) | 減少模式衝突 |
| 負載因子 | α = N/M,超過閾值擴容 | 平衡空間與查找速度 |
| 擴容策略 | 2倍質數,重新哈希所有鍵 | 均攤 O(1) |
O(1 + α),α 為負載因子。良好哈希函數下 α ≈ 1 即近似 O(1)。O(N) (退化為鏈表)。O(M + N)。O(log N)。| 維度 | 鏈地址法 | 開放定址法 |
|---|---|---|
| 實現複雜度 | 低 | 中 (需處理刪除標記/探測序列) |
| 緩存局部性 | 差 (指針跳轉) | 優 (連續內存) |
| 負載因子上限 | 無上限 (可>1) | < 0.7-0.9 |
| 刪除操作 | 簡單 | 需懶惰刪除標記 |
| 適用鍵值大小 | 大對象友好 | 小對象優勢 |
鏈地址法是 **哈希表最經典、最通用** 的衝突解決方案。
掌握:哈希函數去符號取模 → 頭插法鏈表 → 負載因子觸發擴容 → 重新哈希。
現代工業級實現 (Java 8+, C++ unordered_map) 多採用 **鏈表 + 樹化 + 開放定址混合** 策略,根據數據特性自適應。