--- comments: true --- # 6.2   雜湊衝突 上一節提到,**通常情況下雜湊函式的輸入空間遠大於輸出空間**,因此理論上雜湊衝突是不可避免的。比如,輸入空間為全體整數,輸出空間為陣列容量大小,則必然有多個整數對映至同一桶索引。 雜湊衝突會導致查詢結果錯誤,嚴重影響雜湊表的可用性。為了解決該問題,每當遇到雜湊衝突時,我們就進行雜湊表擴容,直至衝突消失為止。此方法簡單粗暴且有效,但效率太低,因為雜湊表擴容需要進行大量的資料搬運與雜湊值計算。為了提升效率,我們可以採用以下策略。 1. 改良雜湊表資料結構,**使得雜湊表可以在出現雜湊衝突時正常工作**。 2. 僅在必要時,即當雜湊衝突比較嚴重時,才執行擴容操作。 雜湊表的結構改良方法主要包括“鏈式位址”和“開放定址”。 ## 6.2.1   鏈式位址 在原始雜湊表中,每個桶僅能儲存一個鍵值對。鏈式位址(separate chaining)將單個元素轉換為鏈結串列,將鍵值對作為鏈結串列節點,將所有發生衝突的鍵值對都儲存在同一鏈結串列中。圖 6-5 展示了一個鏈式位址雜湊表的例子。 ![鏈式位址雜湊表](hash_collision.assets/hash_table_chaining.png){ class="animation-figure" }

圖 6-5   鏈式位址雜湊表

基於鏈式位址實現的雜湊表的操作方法發生了以下變化。 - **查詢元素**:輸入 `key` ,經過雜湊函式得到桶索引,即可訪問鏈結串列頭節點,然後走訪鏈結串列並對比 `key` 以查詢目標鍵值對。 - **新增元素**:首先透過雜湊函式訪問鏈結串列頭節點,然後將節點(鍵值對)新增到鏈結串列中。 - **刪除元素**:根據雜湊函式的結果訪問鏈結串列頭部,接著走訪鏈結串列以查詢目標節點並將其刪除。 鏈式位址存在以下侷限性。 - **佔用空間增大**:鏈結串列包含節點指標,它相比陣列更加耗費記憶體空間。 - **查詢效率降低**:因為需要線性走訪鏈結串列來查詢對應元素。 以下程式碼給出了鏈式位址雜湊表的簡單實現,需要注意兩點。 - 使用串列(動態陣列)代替鏈結串列,從而簡化程式碼。在這種設定下,雜湊表(陣列)包含多個桶,每個桶都是一個串列。 - 以下實現包含雜湊表擴容方法。當負載因子超過 $\frac{2}{3}$ 時,我們將雜湊表擴容至原先的 $2$ 倍。 === "Python" ```python title="hash_map_chaining.py" class HashMapChaining: """鏈式位址雜湊表""" def __init__(self): """建構子""" self.size = 0 # 鍵值對數量 self.capacity = 4 # 雜湊表容量 self.load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 self.extend_ratio = 2 # 擴容倍數 self.buckets = [[] for _ in range(self.capacity)] # 桶陣列 def hash_func(self, key: int) -> int: """雜湊函式""" return key % self.capacity def load_factor(self) -> float: """負載因子""" return self.size / self.capacity def get(self, key: int) -> str | None: """查詢操作""" index = self.hash_func(key) bucket = self.buckets[index] # 走訪桶,若找到 key ,則返回對應 val for pair in bucket: if pair.key == key: return pair.val # 若未找到 key ,則返回 None return None def put(self, key: int, val: str): """新增操作""" # 當負載因子超過閾值時,執行擴容 if self.load_factor() > self.load_thres: self.extend() index = self.hash_func(key) bucket = self.buckets[index] # 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for pair in bucket: if pair.key == key: pair.val = val return # 若無該 key ,則將鍵值對新增至尾部 pair = Pair(key, val) bucket.append(pair) self.size += 1 def remove(self, key: int): """刪除操作""" index = self.hash_func(key) bucket = self.buckets[index] # 走訪桶,從中刪除鍵值對 for pair in bucket: if pair.key == key: bucket.remove(pair) self.size -= 1 break def extend(self): """擴容雜湊表""" # 暫存原雜湊表 buckets = self.buckets # 初始化擴容後的新雜湊表 self.capacity *= self.extend_ratio self.buckets = [[] for _ in range(self.capacity)] self.size = 0 # 將鍵值對從原雜湊表搬運至新雜湊表 for bucket in buckets: for pair in bucket: self.put(pair.key, pair.val) def print(self): """列印雜湊表""" for bucket in self.buckets: res = [] for pair in bucket: res.append(str(pair.key) + " -> " + pair.val) print(res) ``` === "C++" ```cpp title="hash_map_chaining.cpp" /* 鏈式位址雜湊表 */ class HashMapChaining { private: int size; // 鍵值對數量 int capacity; // 雜湊表容量 double loadThres; // 觸發擴容的負載因子閾值 int extendRatio; // 擴容倍數 vector> buckets; // 桶陣列 public: /* 建構子 */ HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) { buckets.resize(capacity); } /* 析構方法 */ ~HashMapChaining() { for (auto &bucket : buckets) { for (Pair *pair : bucket) { // 釋放記憶體 delete pair; } } } /* 雜湊函式 */ int hashFunc(int key) { return key % capacity; } /* 負載因子 */ double loadFactor() { return (double)size / (double)capacity; } /* 查詢操作 */ string get(int key) { int index = hashFunc(key); // 走訪桶,若找到 key ,則返回對應 val for (Pair *pair : buckets[index]) { if (pair->key == key) { return pair->val; } } // 若未找到 key ,則返回空字串 return ""; } /* 新增操作 */ void put(int key, string val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (Pair *pair : buckets[index]) { if (pair->key == key) { pair->val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 buckets[index].push_back(new Pair(key, val)); size++; } /* 刪除操作 */ void remove(int key) { int index = hashFunc(key); auto &bucket = buckets[index]; // 走訪桶,從中刪除鍵值對 for (int i = 0; i < bucket.size(); i++) { if (bucket[i]->key == key) { Pair *tmp = bucket[i]; bucket.erase(bucket.begin() + i); // 從中刪除鍵值對 delete tmp; // 釋放記憶體 size--; return; } } } /* 擴容雜湊表 */ void extend() { // 暫存原雜湊表 vector> bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets.clear(); buckets.resize(capacity); size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (auto &bucket : bucketsTmp) { for (Pair *pair : bucket) { put(pair->key, pair->val); // 釋放記憶體 delete pair; } } } /* 列印雜湊表 */ void print() { for (auto &bucket : buckets) { cout << "["; for (Pair *pair : bucket) { cout << pair->key << " -> " << pair->val << ", "; } cout << "]\n"; } } }; ``` === "Java" ```java title="hash_map_chaining.java" /* 鏈式位址雜湊表 */ class HashMapChaining { int size; // 鍵值對數量 int capacity; // 雜湊表容量 double loadThres; // 觸發擴容的負載因子閾值 int extendRatio; // 擴容倍數 List> buckets; // 桶陣列 /* 建構子 */ public HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = new ArrayList<>(capacity); for (int i = 0; i < capacity; i++) { buckets.add(new ArrayList<>()); } } /* 雜湊函式 */ int hashFunc(int key) { return key % capacity; } /* 負載因子 */ double loadFactor() { return (double) size / capacity; } /* 查詢操作 */ String get(int key) { int index = hashFunc(key); List bucket = buckets.get(index); // 走訪桶,若找到 key ,則返回對應 val for (Pair pair : bucket) { if (pair.key == key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ void put(int key, String val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets.get(index); // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (Pair pair : bucket) { if (pair.key == key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 Pair pair = new Pair(key, val); bucket.add(pair); size++; } /* 刪除操作 */ void remove(int key) { int index = hashFunc(key); List bucket = buckets.get(index); // 走訪桶,從中刪除鍵值對 for (Pair pair : bucket) { if (pair.key == key) { bucket.remove(pair); size--; break; } } } /* 擴容雜湊表 */ void extend() { // 暫存原雜湊表 List> bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets = new ArrayList<>(capacity); for (int i = 0; i < capacity; i++) { buckets.add(new ArrayList<>()); } size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (List bucket : bucketsTmp) { for (Pair pair : bucket) { put(pair.key, pair.val); } } } /* 列印雜湊表 */ void print() { for (List bucket : buckets) { List res = new ArrayList<>(); for (Pair pair : bucket) { res.add(pair.key + " -> " + pair.val); } System.out.println(res); } } } ``` === "C#" ```csharp title="hash_map_chaining.cs" /* 鏈式位址雜湊表 */ class HashMapChaining { int size; // 鍵值對數量 int capacity; // 雜湊表容量 double loadThres; // 觸發擴容的負載因子閾值 int extendRatio; // 擴容倍數 List> buckets; // 桶陣列 /* 建構子 */ public HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } } /* 雜湊函式 */ int HashFunc(int key) { return key % capacity; } /* 負載因子 */ double LoadFactor() { return (double)size / capacity; } /* 查詢操作 */ public string? Get(int key) { int index = HashFunc(key); // 走訪桶,若找到 key ,則返回對應 val foreach (Pair pair in buckets[index]) { if (pair.key == key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ public void Put(int key, string val) { // 當負載因子超過閾值時,執行擴容 if (LoadFactor() > loadThres) { Extend(); } int index = HashFunc(key); // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 foreach (Pair pair in buckets[index]) { if (pair.key == key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 buckets[index].Add(new Pair(key, val)); size++; } /* 刪除操作 */ public void Remove(int key) { int index = HashFunc(key); // 走訪桶,從中刪除鍵值對 foreach (Pair pair in buckets[index].ToList()) { if (pair.key == key) { buckets[index].Remove(pair); size--; break; } } } /* 擴容雜湊表 */ void Extend() { // 暫存原雜湊表 List> bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets = new List>(capacity); for (int i = 0; i < capacity; i++) { buckets.Add([]); } size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 foreach (List bucket in bucketsTmp) { foreach (Pair pair in bucket) { Put(pair.key, pair.val); } } } /* 列印雜湊表 */ public void Print() { foreach (List bucket in buckets) { List res = []; foreach (Pair pair in bucket) { res.Add(pair.key + " -> " + pair.val); } foreach (string kv in res) { Console.WriteLine(kv); } } } } ``` === "Go" ```go title="hash_map_chaining.go" /* 鏈式位址雜湊表 */ type hashMapChaining struct { size int // 鍵值對數量 capacity int // 雜湊表容量 loadThres float64 // 觸發擴容的負載因子閾值 extendRatio int // 擴容倍數 buckets [][]pair // 桶陣列 } /* 建構子 */ func newHashMapChaining() *hashMapChaining { buckets := make([][]pair, 4) for i := 0; i < 4; i++ { buckets[i] = make([]pair, 0) } return &hashMapChaining{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: buckets, } } /* 雜湊函式 */ func (m *hashMapChaining) hashFunc(key int) int { return key % m.capacity } /* 負載因子 */ func (m *hashMapChaining) loadFactor() float64 { return float64(m.size) / float64(m.capacity) } /* 查詢操作 */ func (m *hashMapChaining) get(key int) string { idx := m.hashFunc(key) bucket := m.buckets[idx] // 走訪桶,若找到 key ,則返回對應 val for _, p := range bucket { if p.key == key { return p.val } } // 若未找到 key ,則返回空字串 return "" } /* 新增操作 */ func (m *hashMapChaining) put(key int, val string) { // 當負載因子超過閾值時,執行擴容 if m.loadFactor() > m.loadThres { m.extend() } idx := m.hashFunc(key) // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for i := range m.buckets[idx] { if m.buckets[idx][i].key == key { m.buckets[idx][i].val = val return } } // 若無該 key ,則將鍵值對新增至尾部 p := pair{ key: key, val: val, } m.buckets[idx] = append(m.buckets[idx], p) m.size += 1 } /* 刪除操作 */ func (m *hashMapChaining) remove(key int) { idx := m.hashFunc(key) // 走訪桶,從中刪除鍵值對 for i, p := range m.buckets[idx] { if p.key == key { // 切片刪除 m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) m.size -= 1 break } } } /* 擴容雜湊表 */ func (m *hashMapChaining) extend() { // 暫存原雜湊表 tmpBuckets := make([][]pair, len(m.buckets)) for i := 0; i < len(m.buckets); i++ { tmpBuckets[i] = make([]pair, len(m.buckets[i])) copy(tmpBuckets[i], m.buckets[i]) } // 初始化擴容後的新雜湊表 m.capacity *= m.extendRatio m.buckets = make([][]pair, m.capacity) for i := 0; i < m.capacity; i++ { m.buckets[i] = make([]pair, 0) } m.size = 0 // 將鍵值對從原雜湊表搬運至新雜湊表 for _, bucket := range tmpBuckets { for _, p := range bucket { m.put(p.key, p.val) } } } /* 列印雜湊表 */ func (m *hashMapChaining) print() { var builder strings.Builder for _, bucket := range m.buckets { builder.WriteString("[") for _, p := range bucket { builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") } builder.WriteString("]") fmt.Println(builder.String()) builder.Reset() } } ``` === "Swift" ```swift title="hash_map_chaining.swift" /* 鏈式位址雜湊表 */ class HashMapChaining { var size: Int // 鍵值對數量 var capacity: Int // 雜湊表容量 var loadThres: Double // 觸發擴容的負載因子閾值 var extendRatio: Int // 擴容倍數 var buckets: [[Pair]] // 桶陣列 /* 建構子 */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: [], count: capacity) } /* 雜湊函式 */ func hashFunc(key: Int) -> Int { key % capacity } /* 負載因子 */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* 查詢操作 */ func get(key: Int) -> String? { let index = hashFunc(key: key) let bucket = buckets[index] // 走訪桶,若找到 key ,則返回對應 val for pair in bucket { if pair.key == key { return pair.val } } // 若未找到 key ,則返回 nil return nil } /* 新增操作 */ func put(key: Int, val: String) { // 當負載因子超過閾值時,執行擴容 if loadFactor() > loadThres { extend() } let index = hashFunc(key: key) let bucket = buckets[index] // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for pair in bucket { if pair.key == key { pair.val = val return } } // 若無該 key ,則將鍵值對新增至尾部 let pair = Pair(key: key, val: val) buckets[index].append(pair) size += 1 } /* 刪除操作 */ func remove(key: Int) { let index = hashFunc(key: key) let bucket = buckets[index] // 走訪桶,從中刪除鍵值對 for (pairIndex, pair) in bucket.enumerated() { if pair.key == key { buckets[index].remove(at: pairIndex) size -= 1 break } } } /* 擴容雜湊表 */ func extend() { // 暫存原雜湊表 let bucketsTmp = buckets // 初始化擴容後的新雜湊表 capacity *= extendRatio buckets = Array(repeating: [], count: capacity) size = 0 // 將鍵值對從原雜湊表搬運至新雜湊表 for bucket in bucketsTmp { for pair in bucket { put(key: pair.key, val: pair.val) } } } /* 列印雜湊表 */ func print() { for bucket in buckets { let res = bucket.map { "\($0.key) -> \($0.val)" } Swift.print(res) } } } ``` === "JS" ```javascript title="hash_map_chaining.js" /* 鏈式位址雜湊表 */ class HashMapChaining { #size; // 鍵值對數量 #capacity; // 雜湊表容量 #loadThres; // 觸發擴容的負載因子閾值 #extendRatio; // 擴容倍數 #buckets; // 桶陣列 /* 建構子 */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* 雜湊函式 */ #hashFunc(key) { return key % this.#capacity; } /* 負載因子 */ #loadFactor() { return this.#size / this.#capacity; } /* 查詢操作 */ get(key) { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 走訪桶,若找到 key ,則返回對應 val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ put(key, val) { // 當負載因子超過閾值時,執行擴容 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* 刪除操作 */ remove(key) { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // 走訪桶,從中刪除鍵值對 for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* 擴容雜湊表 */ #extend() { // 暫存原雜湊表 const bucketsTmp = this.#buckets; // 初始化擴容後的新雜湊表 this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* 列印雜湊表 */ print() { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } ``` === "TS" ```typescript title="hash_map_chaining.ts" /* 鏈式位址雜湊表 */ class HashMapChaining { #size: number; // 鍵值對數量 #capacity: number; // 雜湊表容量 #loadThres: number; // 觸發擴容的負載因子閾值 #extendRatio: number; // 擴容倍數 #buckets: Pair[][]; // 桶陣列 /* 建構子 */ constructor() { this.#size = 0; this.#capacity = 4; this.#loadThres = 2.0 / 3.0; this.#extendRatio = 2; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); } /* 雜湊函式 */ #hashFunc(key: number): number { return key % this.#capacity; } /* 負載因子 */ #loadFactor(): number { return this.#size / this.#capacity; } /* 查詢操作 */ get(key: number): string | null { const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 走訪桶,若找到 key ,則返回對應 val for (const pair of bucket) { if (pair.key === key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ put(key: number, val: string): void { // 當負載因子超過閾值時,執行擴容 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } const index = this.#hashFunc(key); const bucket = this.#buckets[index]; // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (const pair of bucket) { if (pair.key === key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 const pair = new Pair(key, val); bucket.push(pair); this.#size++; } /* 刪除操作 */ remove(key: number): void { const index = this.#hashFunc(key); let bucket = this.#buckets[index]; // 走訪桶,從中刪除鍵值對 for (let i = 0; i < bucket.length; i++) { if (bucket[i].key === key) { bucket.splice(i, 1); this.#size--; break; } } } /* 擴容雜湊表 */ #extend(): void { // 暫存原雜湊表 const bucketsTmp = this.#buckets; // 初始化擴容後的新雜湊表 this.#capacity *= this.#extendRatio; this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); this.#size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (const bucket of bucketsTmp) { for (const pair of bucket) { this.put(pair.key, pair.val); } } } /* 列印雜湊表 */ print(): void { for (const bucket of this.#buckets) { let res = []; for (const pair of bucket) { res.push(pair.key + ' -> ' + pair.val); } console.log(res); } } } ``` === "Dart" ```dart title="hash_map_chaining.dart" /* 鏈式位址雜湊表 */ class HashMapChaining { late int size; // 鍵值對數量 late int capacity; // 雜湊表容量 late double loadThres; // 觸發擴容的負載因子閾值 late int extendRatio; // 擴容倍數 late List> buckets; // 桶陣列 /* 建構子 */ HashMapChaining() { size = 0; capacity = 4; loadThres = 2.0 / 3.0; extendRatio = 2; buckets = List.generate(capacity, (_) => []); } /* 雜湊函式 */ int hashFunc(int key) { return key % capacity; } /* 負載因子 */ double loadFactor() { return size / capacity; } /* 查詢操作 */ String? get(int key) { int index = hashFunc(key); List bucket = buckets[index]; // 走訪桶,若找到 key ,則返回對應 val for (Pair pair in bucket) { if (pair.key == key) { return pair.val; } } // 若未找到 key ,則返回 null return null; } /* 新增操作 */ void put(int key, String val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets[index]; // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (Pair pair in bucket) { if (pair.key == key) { pair.val = val; return; } } // 若無該 key ,則將鍵值對新增至尾部 Pair pair = Pair(key, val); bucket.add(pair); size++; } /* 刪除操作 */ void remove(int key) { int index = hashFunc(key); List bucket = buckets[index]; // 走訪桶,從中刪除鍵值對 for (Pair pair in bucket) { if (pair.key == key) { bucket.remove(pair); size--; break; } } } /* 擴容雜湊表 */ void extend() { // 暫存原雜湊表 List> bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets = List.generate(capacity, (_) => []); size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (List bucket in bucketsTmp) { for (Pair pair in bucket) { put(pair.key, pair.val); } } } /* 列印雜湊表 */ void printHashMap() { for (List bucket in buckets) { List res = []; for (Pair pair in bucket) { res.add("${pair.key} -> ${pair.val}"); } print(res); } } } ``` === "Rust" ```rust title="hash_map_chaining.rs" /* 鏈式位址雜湊表 */ struct HashMapChaining { size: i32, capacity: i32, load_thres: f32, extend_ratio: i32, buckets: Vec>, } impl HashMapChaining { /* 建構子 */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![vec![]; 4], } } /* 雜湊函式 */ fn hash_func(&self, key: i32) -> usize { key as usize % self.capacity as usize } /* 負載因子 */ fn load_factor(&self) -> f32 { self.size as f32 / self.capacity as f32 } /* 刪除操作 */ fn remove(&mut self, key: i32) -> Option { let index = self.hash_func(key); let bucket = &mut self.buckets[index]; // 走訪桶,從中刪除鍵值對 for i in 0..bucket.len() { if bucket[i].key == key { let pair = bucket.remove(i); self.size -= 1; return Some(pair.val); } } // 若未找到 key ,則返回 None None } /* 擴容雜湊表 */ fn extend(&mut self) { // 暫存原雜湊表 let buckets_tmp = std::mem::replace(&mut self.buckets, vec![]); // 初始化擴容後的新雜湊表 self.capacity *= self.extend_ratio; self.buckets = vec![Vec::new(); self.capacity as usize]; self.size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for bucket in buckets_tmp { for pair in bucket { self.put(pair.key, pair.val); } } } /* 列印雜湊表 */ fn print(&self) { for bucket in &self.buckets { let mut res = Vec::new(); for pair in bucket { res.push(format!("{} -> {}", pair.key, pair.val)); } println!("{:?}", res); } } /* 新增操作 */ fn put(&mut self, key: i32, val: String) { // 當負載因子超過閾值時,執行擴容 if self.load_factor() > self.load_thres { self.extend(); } let index = self.hash_func(key); let bucket = &mut self.buckets[index]; // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for pair in bucket { if pair.key == key { pair.val = val; return; } } let bucket = &mut self.buckets[index]; // 若無該 key ,則將鍵值對新增至尾部 let pair = Pair { key, val }; bucket.push(pair); self.size += 1; } /* 查詢操作 */ fn get(&self, key: i32) -> Option<&str> { let index = self.hash_func(key); let bucket = &self.buckets[index]; // 走訪桶,若找到 key ,則返回對應 val for pair in bucket { if pair.key == key { return Some(&pair.val); } } // 若未找到 key ,則返回 None None } } ``` === "C" ```c title="hash_map_chaining.c" /* 鏈結串列節點 */ typedef struct Node { Pair *pair; struct Node *next; } Node; /* 鏈式位址雜湊表 */ typedef struct { int size; // 鍵值對數量 int capacity; // 雜湊表容量 double loadThres; // 觸發擴容的負載因子閾值 int extendRatio; // 擴容倍數 Node **buckets; // 桶陣列 } HashMapChaining; /* 建構子 */ HashMapChaining *newHashMapChaining() { HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } return hashMap; } /* 析構函式 */ void delHashMapChaining(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; while (cur) { Node *tmp = cur; cur = cur->next; free(tmp->pair); free(tmp); } } free(hashMap->buckets); free(hashMap); } /* 雜湊函式 */ int hashFunc(HashMapChaining *hashMap, int key) { return key % hashMap->capacity; } /* 負載因子 */ double loadFactor(HashMapChaining *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* 查詢操作 */ char *get(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); // 走訪桶,若找到 key ,則返回對應 val Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { return cur->pair->val; } cur = cur->next; } return ""; // 若未找到 key ,則返回空字串 } /* 新增操作 */ void put(HashMapChaining *hashMap, int key, const char *val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } int index = hashFunc(hashMap, key); // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 Node *cur = hashMap->buckets[index]; while (cur) { if (cur->pair->key == key) { strcpy(cur->pair->val, val); // 若遇到指定 key ,則更新對應 val 並返回 return; } cur = cur->next; } // 若無該 key ,則將鍵值對新增至鏈結串列頭部 Pair *newPair = (Pair *)malloc(sizeof(Pair)); newPair->key = key; strcpy(newPair->val, val); Node *newNode = (Node *)malloc(sizeof(Node)); newNode->pair = newPair; newNode->next = hashMap->buckets[index]; hashMap->buckets[index] = newNode; hashMap->size++; } /* 擴容雜湊表 */ void extend(HashMapChaining *hashMap) { // 暫存原雜湊表 int oldCapacity = hashMap->capacity; Node **oldBuckets = hashMap->buckets; // 初始化擴容後的新雜湊表 hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); for (int i = 0; i < hashMap->capacity; i++) { hashMap->buckets[i] = NULL; } hashMap->size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (int i = 0; i < oldCapacity; i++) { Node *cur = oldBuckets[i]; while (cur) { put(hashMap, cur->pair->key, cur->pair->val); Node *temp = cur; cur = cur->next; // 釋放記憶體 free(temp->pair); free(temp); } } free(oldBuckets); } /* 刪除操作 */ void removeItem(HashMapChaining *hashMap, int key) { int index = hashFunc(hashMap, key); Node *cur = hashMap->buckets[index]; Node *pre = NULL; while (cur) { if (cur->pair->key == key) { // 從中刪除鍵值對 if (pre) { pre->next = cur->next; } else { hashMap->buckets[index] = cur->next; } // 釋放記憶體 free(cur->pair); free(cur); hashMap->size--; return; } pre = cur; cur = cur->next; } } /* 列印雜湊表 */ void print(HashMapChaining *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Node *cur = hashMap->buckets[i]; printf("["); while (cur) { printf("%d -> %s, ", cur->pair->key, cur->pair->val); cur = cur->next; } printf("]\n"); } } ``` === "Kotlin" ```kotlin title="hash_map_chaining.kt" /* 鏈式位址雜湊表 */ class HashMapChaining { var size: Int // 鍵值對數量 var capacity: Int // 雜湊表容量 val loadThres: Double // 觸發擴容的負載因子閾值 val extendRatio: Int // 擴容倍數 var buckets: MutableList> // 桶陣列 /* 建構子 */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = mutableListOf() for (i in 0.. loadThres) { extend() } val index = hashFunc(key) val bucket = buckets[index] // 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for (pair in bucket) { if (pair.key == key) { pair._val = _val return } } // 若無該 key ,則將鍵值對新增至尾部 val pair = Pair(key, _val) bucket.add(pair) size++ } /* 刪除操作 */ fun remove(key: Int) { val index = hashFunc(key) val bucket = buckets[index] // 走訪桶,從中刪除鍵值對 for (pair in bucket) { if (pair.key == key) { bucket.remove(pair) size-- break } } } /* 擴容雜湊表 */ fun extend() { // 暫存原雜湊表 val bucketsTmp = buckets // 初始化擴容後的新雜湊表 capacity *= extendRatio // mutablelist 無固定大小 buckets = mutableListOf() for (i in 0..() for (pair in bucket) { val k = pair.key val v = pair._val res.add("$k -> $v") } println(res) } } } ``` === "Ruby" ```ruby title="hash_map_chaining.rb" ### 鍵式位址雜湊表 ### class HashMapChaining ### 建構子 ### def initialize @size = 0 # 鍵值對數量 @capacity = 4 # 雜湊表容量 @load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 @extend_ratio = 2 # 擴容倍數 @buckets = Array.new(@capacity) { [] } # 桶陣列 end ### 雜湊函式 ### def hash_func(key) key % @capacity end ### 負載因子 ### def load_factor @size / @capacity end ### 查詢操作 ### def get(key) index = hash_func(key) bucket = @buckets[index] # 走訪桶,若找到 key ,則返回對應 val for pair in bucket return pair.val if pair.key == key end # 若未找到 key , 則返回 nil nil end ### 新增操作 ### def put(key, val) # 當負載因子超過閾值時,執行擴容 extend if load_factor > @load_thres index = hash_func(key) bucket = @buckets[index] # 走訪桶,若遇到指定 key ,則更新對應 val 並返回 for pair in bucket if pair.key == key pair.val = val return end end # 若無該 key ,則將鍵值對新增至尾部 pair = Pair.new(key, val) bucket << pair @size += 1 end ### 刪除操作 ### def remove(key) index = hash_func(key) bucket = @buckets[index] # 走訪桶,從中刪除鍵值對 for pair in bucket if pair.key == key bucket.delete(pair) @size -= 1 break end end end ### 擴容雜湊表 ### def extend # 暫存原雜湊表 buckets = @buckets # 初始化擴容後的新雜湊表 @capacity *= @extend_ratio @buckets = Array.new(@capacity) { [] } @size = 0 # 將鍵值對從原雜湊表搬運至新雜湊表 for bucket in buckets for pair in bucket put(pair.key, pair.val) end end end ### 列印雜湊表 ### def print for bucket in @buckets res = [] for pair in bucket res << "#{pair.key} -> #{pair.val}" end pp res end end end ``` === "Zig" ```zig title="hash_map_chaining.zig" [class]{HashMapChaining}-[func]{} ``` ??? pythontutor "視覺化執行"
值得注意的是,當鏈結串列很長時,查詢效率 $O(n)$ 很差。**此時可以將鏈結串列轉換為“AVL 樹”或“紅黑樹”**,從而將查詢操作的時間複雜度最佳化至 $O(\log n)$ 。 ## 6.2.2   開放定址 開放定址(open addressing)不引入額外的資料結構,而是透過“多次探測”來處理雜湊衝突,探測方式主要包括線性探查、平方探測和多次雜湊等。 下面以線性探查為例,介紹開放定址雜湊表的工作機制。 ### 1.   線性探查 線性探查採用固定步長的線性搜尋來進行探測,其操作方法與普通雜湊表有所不同。 - **插入元素**:透過雜湊函式計算桶索引,若發現桶內已有元素,則從衝突位置向後線性走訪(步長通常為 $1$ ),直至找到空桶,將元素插入其中。 - **查詢元素**:若發現雜湊衝突,則使用相同步長向後進行線性走訪,直到找到對應元素,返回 `value` 即可;如果遇到空桶,說明目標元素不在雜湊表中,返回 `None` 。 圖 6-6 展示了開放定址(線性探查)雜湊表的鍵值對分佈。根據此雜湊函式,最後兩位相同的 `key` 都會被對映到相同的桶。而透過線性探查,它們被依次儲存在該桶以及之下的桶中。 ![開放定址(線性探查)雜湊表的鍵值對分佈](hash_collision.assets/hash_table_linear_probing.png){ class="animation-figure" }

圖 6-6   開放定址(線性探查)雜湊表的鍵值對分佈

然而,**線性探查容易產生“聚集現象”**。具體來說,陣列中連續被佔用的位置越長,這些連續位置發生雜湊衝突的可能性越大,從而進一步促使該位置的聚堆積生長,形成惡性迴圈,最終導致增刪查改操作效率劣化。 值得注意的是,**我們不能在開放定址雜湊表中直接刪除元素**。這是因為刪除元素會在陣列內產生一個空桶 `None` ,而當查詢元素時,線性探查到該空桶就會返回,因此在該空桶之下的元素都無法再被訪問到,程式可能誤判這些元素不存在,如圖 6-7 所示。 ![在開放定址中刪除元素導致的查詢問題](hash_collision.assets/hash_table_open_addressing_deletion.png){ class="animation-figure" }

圖 6-7   在開放定址中刪除元素導致的查詢問題

為了解決該問題,我們可以採用懶刪除(lazy deletion)機制:它不直接從雜湊表中移除元素,**而是利用一個常數 `TOMBSTONE` 來標記這個桶**。在該機制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置鍵值對。但不同的是,線性探查到 `TOMBSTONE` 時應該繼續走訪,因為其之下可能還存在鍵值對。 然而,**懶刪除可能會加速雜湊表的效能退化**。這是因為每次刪除操作都會產生一個刪除標記,隨著 `TOMBSTONE` 的增加,搜尋時間也會增加,因為線性探查可能需要跳過多個 `TOMBSTONE` 才能找到目標元素。 為此,考慮線上性探查中記錄遇到的首個 `TOMBSTONE` 的索引,並將搜尋到的目標元素與該 `TOMBSTONE` 交換位置。這樣做的好處是當每次查詢或新增元素時,元素會被移動至距離理想位置(探測起始點)更近的桶,從而最佳化查詢效率。 以下程式碼實現了一個包含懶刪除的開放定址(線性探查)雜湊表。為了更加充分地使用雜湊表的空間,我們將雜湊表看作一個“環形陣列”,當越過陣列尾部時,回到頭部繼續走訪。 === "Python" ```python title="hash_map_open_addressing.py" class HashMapOpenAddressing: """開放定址雜湊表""" def __init__(self): """建構子""" self.size = 0 # 鍵值對數量 self.capacity = 4 # 雜湊表容量 self.load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 self.extend_ratio = 2 # 擴容倍數 self.buckets: list[Pair | None] = [None] * self.capacity # 桶陣列 self.TOMBSTONE = Pair(-1, "-1") # 刪除標記 def hash_func(self, key: int) -> int: """雜湊函式""" return key % self.capacity def load_factor(self) -> float: """負載因子""" return self.size / self.capacity def find_bucket(self, key: int) -> int: """搜尋 key 對應的桶索引""" index = self.hash_func(key) first_tombstone = -1 # 線性探查,當遇到空桶時跳出 while self.buckets[index] is not None: # 若遇到 key ,返回對應的桶索引 if self.buckets[index].key == key: # 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if first_tombstone != -1: self.buckets[first_tombstone] = self.buckets[index] self.buckets[index] = self.TOMBSTONE return first_tombstone # 返回移動後的桶索引 return index # 返回桶索引 # 記錄遇到的首個刪除標記 if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: first_tombstone = index # 計算桶索引,越過尾部則返回頭部 index = (index + 1) % self.capacity # 若 key 不存在,則返回新增點的索引 return index if first_tombstone == -1 else first_tombstone def get(self, key: int) -> str: """查詢操作""" # 搜尋 key 對應的桶索引 index = self.find_bucket(key) # 若找到鍵值對,則返回對應 val if self.buckets[index] not in [None, self.TOMBSTONE]: return self.buckets[index].val # 若鍵值對不存在,則返回 None return None def put(self, key: int, val: str): """新增操作""" # 當負載因子超過閾值時,執行擴容 if self.load_factor() > self.load_thres: self.extend() # 搜尋 key 對應的桶索引 index = self.find_bucket(key) # 若找到鍵值對,則覆蓋 val 並返回 if self.buckets[index] not in [None, self.TOMBSTONE]: self.buckets[index].val = val return # 若鍵值對不存在,則新增該鍵值對 self.buckets[index] = Pair(key, val) self.size += 1 def remove(self, key: int): """刪除操作""" # 搜尋 key 對應的桶索引 index = self.find_bucket(key) # 若找到鍵值對,則用刪除標記覆蓋它 if self.buckets[index] not in [None, self.TOMBSTONE]: self.buckets[index] = self.TOMBSTONE self.size -= 1 def extend(self): """擴容雜湊表""" # 暫存原雜湊表 buckets_tmp = self.buckets # 初始化擴容後的新雜湊表 self.capacity *= self.extend_ratio self.buckets = [None] * self.capacity self.size = 0 # 將鍵值對從原雜湊表搬運至新雜湊表 for pair in buckets_tmp: if pair not in [None, self.TOMBSTONE]: self.put(pair.key, pair.val) def print(self): """列印雜湊表""" for pair in self.buckets: if pair is None: print("None") elif pair is self.TOMBSTONE: print("TOMBSTONE") else: print(pair.key, "->", pair.val) ``` === "C++" ```cpp title="hash_map_open_addressing.cpp" /* 開放定址雜湊表 */ class HashMapOpenAddressing { private: int size; // 鍵值對數量 int capacity = 4; // 雜湊表容量 const double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 const int extendRatio = 2; // 擴容倍數 vector buckets; // 桶陣列 Pair *TOMBSTONE = new Pair(-1, "-1"); // 刪除標記 public: /* 建構子 */ HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { } /* 析構方法 */ ~HashMapOpenAddressing() { for (Pair *pair : buckets) { if (pair != nullptr && pair != TOMBSTONE) { delete pair; } } delete TOMBSTONE; } /* 雜湊函式 */ int hashFunc(int key) { return key % capacity; } /* 負載因子 */ double loadFactor() { return (double)size / capacity; } /* 搜尋 key 對應的桶索引 */ int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (buckets[index] != nullptr) { // 若遇到 key ,返回對應的桶索引 if (buckets[index]->key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查詢操作 */ string get(int key) { // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則返回對應 val if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { return buckets[index]->val; } // 若鍵值對不存在,則返回空字串 return ""; } /* 新增操作 */ void put(int key, string val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend(); } // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { buckets[index]->val = val; return; } // 若鍵值對不存在,則新增該鍵值對 buckets[index] = new Pair(key, val); size++; } /* 刪除操作 */ void remove(int key) { // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { delete buckets[index]; buckets[index] = TOMBSTONE; size--; } } /* 擴容雜湊表 */ void extend() { // 暫存原雜湊表 vector bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets = vector(capacity, nullptr); size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (Pair *pair : bucketsTmp) { if (pair != nullptr && pair != TOMBSTONE) { put(pair->key, pair->val); delete pair; } } } /* 列印雜湊表 */ void print() { for (Pair *pair : buckets) { if (pair == nullptr) { cout << "nullptr" << endl; } else if (pair == TOMBSTONE) { cout << "TOMBSTONE" << endl; } else { cout << pair->key << " -> " << pair->val << endl; } } } }; ``` === "Java" ```java title="hash_map_open_addressing.java" /* 開放定址雜湊表 */ class HashMapOpenAddressing { private int size; // 鍵值對數量 private int capacity = 4; // 雜湊表容量 private final double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 private final int extendRatio = 2; // 擴容倍數 private Pair[] buckets; // 桶陣列 private final Pair TOMBSTONE = new Pair(-1, "-1"); // 刪除標記 /* 建構子 */ public HashMapOpenAddressing() { size = 0; buckets = new Pair[capacity]; } /* 雜湊函式 */ private int hashFunc(int key) { return key % capacity; } /* 負載因子 */ private double loadFactor() { return (double) size / capacity; } /* 搜尋 key 對應的桶索引 */ private int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (buckets[index] != null) { // 若遇到 key ,返回對應的桶索引 if (buckets[index].key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查詢操作 */ public String get(int key) { // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則返回對應 val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index].val; } // 若鍵值對不存在,則返回 null return null; } /* 新增操作 */ public void put(int key, String val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend(); } // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index].val = val; return; } // 若鍵值對不存在,則新增該鍵值對 buckets[index] = new Pair(key, val); size++; } /* 刪除操作 */ public void remove(int key) { // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE; size--; } } /* 擴容雜湊表 */ private void extend() { // 暫存原雜湊表 Pair[] bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets = new Pair[capacity]; size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (Pair pair : bucketsTmp) { if (pair != null && pair != TOMBSTONE) { put(pair.key, pair.val); } } } /* 列印雜湊表 */ public void print() { for (Pair pair : buckets) { if (pair == null) { System.out.println("null"); } else if (pair == TOMBSTONE) { System.out.println("TOMBSTONE"); } else { System.out.println(pair.key + " -> " + pair.val); } } } } ``` === "C#" ```csharp title="hash_map_open_addressing.cs" /* 開放定址雜湊表 */ class HashMapOpenAddressing { int size; // 鍵值對數量 int capacity = 4; // 雜湊表容量 double loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 int extendRatio = 2; // 擴容倍數 Pair[] buckets; // 桶陣列 Pair TOMBSTONE = new(-1, "-1"); // 刪除標記 /* 建構子 */ public HashMapOpenAddressing() { size = 0; buckets = new Pair[capacity]; } /* 雜湊函式 */ int HashFunc(int key) { return key % capacity; } /* 負載因子 */ double LoadFactor() { return (double)size / capacity; } /* 搜尋 key 對應的桶索引 */ int FindBucket(int key) { int index = HashFunc(key); int firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (buckets[index] != null) { // 若遇到 key ,返回對應的桶索引 if (buckets[index].key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查詢操作 */ public string? Get(int key) { // 搜尋 key 對應的桶索引 int index = FindBucket(key); // 若找到鍵值對,則返回對應 val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index].val; } // 若鍵值對不存在,則返回 null return null; } /* 新增操作 */ public void Put(int key, string val) { // 當負載因子超過閾值時,執行擴容 if (LoadFactor() > loadThres) { Extend(); } // 搜尋 key 對應的桶索引 int index = FindBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index].val = val; return; } // 若鍵值對不存在,則新增該鍵值對 buckets[index] = new Pair(key, val); size++; } /* 刪除操作 */ public void Remove(int key) { // 搜尋 key 對應的桶索引 int index = FindBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE; size--; } } /* 擴容雜湊表 */ void Extend() { // 暫存原雜湊表 Pair[] bucketsTmp = buckets; // 初始化擴容後的新雜湊表 capacity *= extendRatio; buckets = new Pair[capacity]; size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 foreach (Pair pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { Put(pair.key, pair.val); } } } /* 列印雜湊表 */ public void Print() { foreach (Pair pair in buckets) { if (pair == null) { Console.WriteLine("null"); } else if (pair == TOMBSTONE) { Console.WriteLine("TOMBSTONE"); } else { Console.WriteLine(pair.key + " -> " + pair.val); } } } } ``` === "Go" ```go title="hash_map_open_addressing.go" /* 開放定址雜湊表 */ type hashMapOpenAddressing struct { size int // 鍵值對數量 capacity int // 雜湊表容量 loadThres float64 // 觸發擴容的負載因子閾值 extendRatio int // 擴容倍數 buckets []*pair // 桶陣列 TOMBSTONE *pair // 刪除標記 } /* 建構子 */ func newHashMapOpenAddressing() *hashMapOpenAddressing { return &hashMapOpenAddressing{ size: 0, capacity: 4, loadThres: 2.0 / 3.0, extendRatio: 2, buckets: make([]*pair, 4), TOMBSTONE: &pair{-1, "-1"}, } } /* 雜湊函式 */ func (h *hashMapOpenAddressing) hashFunc(key int) int { return key % h.capacity // 根據鍵計算雜湊值 } /* 負載因子 */ func (h *hashMapOpenAddressing) loadFactor() float64 { return float64(h.size) / float64(h.capacity) // 計算當前負載因子 } /* 搜尋 key 對應的桶索引 */ func (h *hashMapOpenAddressing) findBucket(key int) int { index := h.hashFunc(key) // 獲取初始索引 firstTombstone := -1 // 記錄遇到的第一個TOMBSTONE的位置 for h.buckets[index] != nil { if h.buckets[index].key == key { if firstTombstone != -1 { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 h.buckets[firstTombstone] = h.buckets[index] h.buckets[index] = h.TOMBSTONE return firstTombstone // 返回移動後的桶索引 } return index // 返回找到的索引 } if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { firstTombstone = index // 記錄遇到的首個刪除標記的位置 } index = (index + 1) % h.capacity // 線性探查,越過尾部則返回頭部 } // 若 key 不存在,則返回新增點的索引 if firstTombstone != -1 { return firstTombstone } return index } /* 查詢操作 */ func (h *hashMapOpenAddressing) get(key int) string { index := h.findBucket(key) // 搜尋 key 對應的桶索引 if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { return h.buckets[index].val // 若找到鍵值對,則返回對應 val } return "" // 若鍵值對不存在,則返回 "" } /* 新增操作 */ func (h *hashMapOpenAddressing) put(key int, val string) { if h.loadFactor() > h.loadThres { h.extend() // 當負載因子超過閾值時,執行擴容 } index := h.findBucket(key) // 搜尋 key 對應的桶索引 if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { h.buckets[index] = &pair{key, val} // 若鍵值對不存在,則新增該鍵值對 h.size++ } else { h.buckets[index].val = val // 若找到鍵值對,則覆蓋 val } } /* 刪除操作 */ func (h *hashMapOpenAddressing) remove(key int) { index := h.findBucket(key) // 搜尋 key 對應的桶索引 if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { h.buckets[index] = h.TOMBSTONE // 若找到鍵值對,則用刪除標記覆蓋它 h.size-- } } /* 擴容雜湊表 */ func (h *hashMapOpenAddressing) extend() { oldBuckets := h.buckets // 暫存原雜湊表 h.capacity *= h.extendRatio // 更新容量 h.buckets = make([]*pair, h.capacity) // 初始化擴容後的新雜湊表 h.size = 0 // 重置大小 // 將鍵值對從原雜湊表搬運至新雜湊表 for _, pair := range oldBuckets { if pair != nil && pair != h.TOMBSTONE { h.put(pair.key, pair.val) } } } /* 列印雜湊表 */ func (h *hashMapOpenAddressing) print() { for _, pair := range h.buckets { if pair == nil { fmt.Println("nil") } else if pair == h.TOMBSTONE { fmt.Println("TOMBSTONE") } else { fmt.Printf("%d -> %s\n", pair.key, pair.val) } } } ``` === "Swift" ```swift title="hash_map_open_addressing.swift" /* 開放定址雜湊表 */ class HashMapOpenAddressing { var size: Int // 鍵值對數量 var capacity: Int // 雜湊表容量 var loadThres: Double // 觸發擴容的負載因子閾值 var extendRatio: Int // 擴容倍數 var buckets: [Pair?] // 桶陣列 var TOMBSTONE: Pair // 刪除標記 /* 建構子 */ init() { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = Array(repeating: nil, count: capacity) TOMBSTONE = Pair(key: -1, val: "-1") } /* 雜湊函式 */ func hashFunc(key: Int) -> Int { key % capacity } /* 負載因子 */ func loadFactor() -> Double { Double(size) / Double(capacity) } /* 搜尋 key 對應的桶索引 */ func findBucket(key: Int) -> Int { var index = hashFunc(key: key) var firstTombstone = -1 // 線性探查,當遇到空桶時跳出 while buckets[index] != nil { // 若遇到 key ,返回對應的桶索引 if buckets[index]!.key == key { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if firstTombstone != -1 { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // 返回移動後的桶索引 } return index // 返回桶索引 } // 記錄遇到的首個刪除標記 if firstTombstone == -1 && buckets[index] == TOMBSTONE { firstTombstone = index } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % capacity } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone } /* 查詢操作 */ func get(key: Int) -> String? { // 搜尋 key 對應的桶索引 let index = findBucket(key: key) // 若找到鍵值對,則返回對應 val if buckets[index] != nil, buckets[index] != TOMBSTONE { return buckets[index]!.val } // 若鍵值對不存在,則返回 null return nil } /* 新增操作 */ func put(key: Int, val: String) { // 當負載因子超過閾值時,執行擴容 if loadFactor() > loadThres { extend() } // 搜尋 key 對應的桶索引 let index = findBucket(key: key) // 若找到鍵值對,則覆蓋 val 並返回 if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index]!.val = val return } // 若鍵值對不存在,則新增該鍵值對 buckets[index] = Pair(key: key, val: val) size += 1 } /* 刪除操作 */ func remove(key: Int) { // 搜尋 key 對應的桶索引 let index = findBucket(key: key) // 若找到鍵值對,則用刪除標記覆蓋它 if buckets[index] != nil, buckets[index] != TOMBSTONE { buckets[index] = TOMBSTONE size -= 1 } } /* 擴容雜湊表 */ func extend() { // 暫存原雜湊表 let bucketsTmp = buckets // 初始化擴容後的新雜湊表 capacity *= extendRatio buckets = Array(repeating: nil, count: capacity) size = 0 // 將鍵值對從原雜湊表搬運至新雜湊表 for pair in bucketsTmp { if let pair, pair != TOMBSTONE { put(key: pair.key, val: pair.val) } } } /* 列印雜湊表 */ func print() { for pair in buckets { if pair == nil { Swift.print("null") } else if pair == TOMBSTONE { Swift.print("TOMBSTONE") } else { Swift.print("\(pair!.key) -> \(pair!.val)") } } } } ``` === "JS" ```javascript title="hash_map_open_addressing.js" /* 開放定址雜湊表 */ class HashMapOpenAddressing { #size; // 鍵值對數量 #capacity; // 雜湊表容量 #loadThres; // 觸發擴容的負載因子閾值 #extendRatio; // 擴容倍數 #buckets; // 桶陣列 #TOMBSTONE; // 刪除標記 /* 建構子 */ constructor() { this.#size = 0; // 鍵值對數量 this.#capacity = 4; // 雜湊表容量 this.#loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 this.#extendRatio = 2; // 擴容倍數 this.#buckets = Array(this.#capacity).fill(null); // 桶陣列 this.#TOMBSTONE = new Pair(-1, '-1'); // 刪除標記 } /* 雜湊函式 */ #hashFunc(key) { return key % this.#capacity; } /* 負載因子 */ #loadFactor() { return this.#size / this.#capacity; } /* 搜尋 key 對應的桶索引 */ #findBucket(key) { let index = this.#hashFunc(key); let firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (this.#buckets[index] !== null) { // 若遇到 key ,返回對應的桶索引 if (this.#buckets[index].key === key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone !== -1) { this.#buckets[firstTombstone] = this.#buckets[index]; this.#buckets[index] = this.#TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if ( firstTombstone === -1 && this.#buckets[index] === this.#TOMBSTONE ) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % this.#capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone === -1 ? index : firstTombstone; } /* 查詢操作 */ get(key) { // 搜尋 key 對應的桶索引 const index = this.#findBucket(key); // 若找到鍵值對,則返回對應 val if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { return this.#buckets[index].val; } // 若鍵值對不存在,則返回 null return null; } /* 新增操作 */ put(key, val) { // 當負載因子超過閾值時,執行擴容 if (this.#loadFactor() > this.#loadThres) { this.#extend(); } // 搜尋 key 對應的桶索引 const index = this.#findBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index].val = val; return; } // 若鍵值對不存在,則新增該鍵值對 this.#buckets[index] = new Pair(key, val); this.#size++; } /* 刪除操作 */ remove(key) { // 搜尋 key 對應的桶索引 const index = this.#findBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if ( this.#buckets[index] !== null && this.#buckets[index] !== this.#TOMBSTONE ) { this.#buckets[index] = this.#TOMBSTONE; this.#size--; } } /* 擴容雜湊表 */ #extend() { // 暫存原雜湊表 const bucketsTmp = this.#buckets; // 初始化擴容後的新雜湊表 this.#capacity *= this.#extendRatio; this.#buckets = Array(this.#capacity).fill(null); this.#size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (const pair of bucketsTmp) { if (pair !== null && pair !== this.#TOMBSTONE) { this.put(pair.key, pair.val); } } } /* 列印雜湊表 */ print() { for (const pair of this.#buckets) { if (pair === null) { console.log('null'); } else if (pair === this.#TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } ``` === "TS" ```typescript title="hash_map_open_addressing.ts" /* 開放定址雜湊表 */ class HashMapOpenAddressing { private size: number; // 鍵值對數量 private capacity: number; // 雜湊表容量 private loadThres: number; // 觸發擴容的負載因子閾值 private extendRatio: number; // 擴容倍數 private buckets: Array; // 桶陣列 private TOMBSTONE: Pair; // 刪除標記 /* 建構子 */ constructor() { this.size = 0; // 鍵值對數量 this.capacity = 4; // 雜湊表容量 this.loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 this.extendRatio = 2; // 擴容倍數 this.buckets = Array(this.capacity).fill(null); // 桶陣列 this.TOMBSTONE = new Pair(-1, '-1'); // 刪除標記 } /* 雜湊函式 */ private hashFunc(key: number): number { return key % this.capacity; } /* 負載因子 */ private loadFactor(): number { return this.size / this.capacity; } /* 搜尋 key 對應的桶索引 */ private findBucket(key: number): number { let index = this.hashFunc(key); let firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (this.buckets[index] !== null) { // 若遇到 key ,返回對應的桶索引 if (this.buckets[index]!.key === key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone !== -1) { this.buckets[firstTombstone] = this.buckets[index]; this.buckets[index] = this.TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if ( firstTombstone === -1 && this.buckets[index] === this.TOMBSTONE ) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % this.capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone === -1 ? index : firstTombstone; } /* 查詢操作 */ get(key: number): string | null { // 搜尋 key 對應的桶索引 const index = this.findBucket(key); // 若找到鍵值對,則返回對應 val if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { return this.buckets[index]!.val; } // 若鍵值對不存在,則返回 null return null; } /* 新增操作 */ put(key: number, val: string): void { // 當負載因子超過閾值時,執行擴容 if (this.loadFactor() > this.loadThres) { this.extend(); } // 搜尋 key 對應的桶索引 const index = this.findBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index]!.val = val; return; } // 若鍵值對不存在,則新增該鍵值對 this.buckets[index] = new Pair(key, val); this.size++; } /* 刪除操作 */ remove(key: number): void { // 搜尋 key 對應的桶索引 const index = this.findBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if ( this.buckets[index] !== null && this.buckets[index] !== this.TOMBSTONE ) { this.buckets[index] = this.TOMBSTONE; this.size--; } } /* 擴容雜湊表 */ private extend(): void { // 暫存原雜湊表 const bucketsTmp = this.buckets; // 初始化擴容後的新雜湊表 this.capacity *= this.extendRatio; this.buckets = Array(this.capacity).fill(null); this.size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (const pair of bucketsTmp) { if (pair !== null && pair !== this.TOMBSTONE) { this.put(pair.key, pair.val); } } } /* 列印雜湊表 */ print(): void { for (const pair of this.buckets) { if (pair === null) { console.log('null'); } else if (pair === this.TOMBSTONE) { console.log('TOMBSTONE'); } else { console.log(pair.key + ' -> ' + pair.val); } } } } ``` === "Dart" ```dart title="hash_map_open_addressing.dart" /* 開放定址雜湊表 */ class HashMapOpenAddressing { late int _size; // 鍵值對數量 int _capacity = 4; // 雜湊表容量 double _loadThres = 2.0 / 3.0; // 觸發擴容的負載因子閾值 int _extendRatio = 2; // 擴容倍數 late List _buckets; // 桶陣列 Pair _TOMBSTONE = Pair(-1, "-1"); // 刪除標記 /* 建構子 */ HashMapOpenAddressing() { _size = 0; _buckets = List.generate(_capacity, (index) => null); } /* 雜湊函式 */ int hashFunc(int key) { return key % _capacity; } /* 負載因子 */ double loadFactor() { return _size / _capacity; } /* 搜尋 key 對應的桶索引 */ int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (_buckets[index] != null) { // 若遇到 key ,返回對應的桶索引 if (_buckets[index]!.key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { _buckets[firstTombstone] = _buckets[index]; _buckets[index] = _TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % _capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查詢操作 */ String? get(int key) { // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則返回對應 val if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { return _buckets[index]!.val; } // 若鍵值對不存在,則返回 null return null; } /* 新增操作 */ void put(int key, String val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > _loadThres) { extend(); } // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index]!.val = val; return; } // 若鍵值對不存在,則新增該鍵值對 _buckets[index] = new Pair(key, val); _size++; } /* 刪除操作 */ void remove(int key) { // 搜尋 key 對應的桶索引 int index = findBucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { _buckets[index] = _TOMBSTONE; _size--; } } /* 擴容雜湊表 */ void extend() { // 暫存原雜湊表 List bucketsTmp = _buckets; // 初始化擴容後的新雜湊表 _capacity *= _extendRatio; _buckets = List.generate(_capacity, (index) => null); _size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (Pair? pair in bucketsTmp) { if (pair != null && pair != _TOMBSTONE) { put(pair.key, pair.val); } } } /* 列印雜湊表 */ void printHashMap() { for (Pair? pair in _buckets) { if (pair == null) { print("null"); } else if (pair == _TOMBSTONE) { print("TOMBSTONE"); } else { print("${pair.key} -> ${pair.val}"); } } } } ``` === "Rust" ```rust title="hash_map_open_addressing.rs" /* 開放定址雜湊表 */ struct HashMapOpenAddressing { size: usize, // 鍵值對數量 capacity: usize, // 雜湊表容量 load_thres: f64, // 觸發擴容的負載因子閾值 extend_ratio: usize, // 擴容倍數 buckets: Vec>, // 桶陣列 TOMBSTONE: Option, // 刪除標記 } impl HashMapOpenAddressing { /* 建構子 */ fn new() -> Self { Self { size: 0, capacity: 4, load_thres: 2.0 / 3.0, extend_ratio: 2, buckets: vec![None; 4], TOMBSTONE: Some(Pair { key: -1, val: "-1".to_string(), }), } } /* 雜湊函式 */ fn hash_func(&self, key: i32) -> usize { (key % self.capacity as i32) as usize } /* 負載因子 */ fn load_factor(&self) -> f64 { self.size as f64 / self.capacity as f64 } /* 搜尋 key 對應的桶索引 */ fn find_bucket(&mut self, key: i32) -> usize { let mut index = self.hash_func(key); let mut first_tombstone = -1; // 線性探查,當遇到空桶時跳出 while self.buckets[index].is_some() { // 若遇到 key,返回對應的桶索引 if self.buckets[index].as_ref().unwrap().key == key { // 若之前遇到了刪除標記,則將建值對移動至該索引 if first_tombstone != -1 { self.buckets[first_tombstone as usize] = self.buckets[index].take(); self.buckets[index] = self.TOMBSTONE.clone(); return first_tombstone as usize; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { first_tombstone = index as i32; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % self.capacity; } // 若 key 不存在,則返回新增點的索引 if first_tombstone == -1 { index } else { first_tombstone as usize } } /* 查詢操作 */ fn get(&mut self, key: i32) -> Option<&str> { // 搜尋 key 對應的桶索引 let index = self.find_bucket(key); // 若找到鍵值對,則返回對應 val if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { return self.buckets[index].as_ref().map(|pair| &pair.val as &str); } // 若鍵值對不存在,則返回 null None } /* 新增操作 */ fn put(&mut self, key: i32, val: String) { // 當負載因子超過閾值時,執行擴容 if self.load_factor() > self.load_thres { self.extend(); } // 搜尋 key 對應的桶索引 let index = self.find_bucket(key); // 若找到鍵值對,則覆蓋 val 並返回 if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index].as_mut().unwrap().val = val; return; } // 若鍵值對不存在,則新增該鍵值對 self.buckets[index] = Some(Pair { key, val }); self.size += 1; } /* 刪除操作 */ fn remove(&mut self, key: i32) { // 搜尋 key 對應的桶索引 let index = self.find_bucket(key); // 若找到鍵值對,則用刪除標記覆蓋它 if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { self.buckets[index] = self.TOMBSTONE.clone(); self.size -= 1; } } /* 擴容雜湊表 */ fn extend(&mut self) { // 暫存原雜湊表 let buckets_tmp = self.buckets.clone(); // 初始化擴容後的新雜湊表 self.capacity *= self.extend_ratio; self.buckets = vec![None; self.capacity]; self.size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for pair in buckets_tmp { if pair.is_none() || pair == self.TOMBSTONE { continue; } let pair = pair.unwrap(); self.put(pair.key, pair.val); } } /* 列印雜湊表 */ fn print(&self) { for pair in &self.buckets { if pair.is_none() { println!("null"); } else if pair == &self.TOMBSTONE { println!("TOMBSTONE"); } else { let pair = pair.as_ref().unwrap(); println!("{} -> {}", pair.key, pair.val); } } } } ``` === "C" ```c title="hash_map_open_addressing.c" /* 開放定址雜湊表 */ typedef struct { int size; // 鍵值對數量 int capacity; // 雜湊表容量 double loadThres; // 觸發擴容的負載因子閾值 int extendRatio; // 擴容倍數 Pair **buckets; // 桶陣列 Pair *TOMBSTONE; // 刪除標記 } HashMapOpenAddressing; /* 建構子 */ HashMapOpenAddressing *newHashMapOpenAddressing() { HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); hashMap->size = 0; hashMap->capacity = 4; hashMap->loadThres = 2.0 / 3.0; hashMap->extendRatio = 2; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); hashMap->TOMBSTONE->key = -1; hashMap->TOMBSTONE->val = "-1"; return hashMap; } /* 析構函式 */ void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { free(pair->val); free(pair); } } free(hashMap->buckets); free(hashMap->TOMBSTONE); free(hashMap); } /* 雜湊函式 */ int hashFunc(HashMapOpenAddressing *hashMap, int key) { return key % hashMap->capacity; } /* 負載因子 */ double loadFactor(HashMapOpenAddressing *hashMap) { return (double)hashMap->size / (double)hashMap->capacity; } /* 搜尋 key 對應的桶索引 */ int findBucket(HashMapOpenAddressing *hashMap, int key) { int index = hashFunc(hashMap, key); int firstTombstone = -1; // 線性探查,當遇到空桶時跳出 while (hashMap->buckets[index] != NULL) { // 若遇到 key ,返回對應的桶索引 if (hashMap->buckets[index]->key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { hashMap->buckets[firstTombstone] = hashMap->buckets[index]; hashMap->buckets[index] = hashMap->TOMBSTONE; return firstTombstone; // 返回移動後的桶索引 } return index; // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { firstTombstone = index; } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % hashMap->capacity; } // 若 key 不存在,則返回新增點的索引 return firstTombstone == -1 ? index : firstTombstone; } /* 查詢操作 */ char *get(HashMapOpenAddressing *hashMap, int key) { // 搜尋 key 對應的桶索引 int index = findBucket(hashMap, key); // 若找到鍵值對,則返回對應 val if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { return hashMap->buckets[index]->val; } // 若鍵值對不存在,則返回空字串 return ""; } /* 新增操作 */ void put(HashMapOpenAddressing *hashMap, int key, char *val) { // 當負載因子超過閾值時,執行擴容 if (loadFactor(hashMap) > hashMap->loadThres) { extend(hashMap); } // 搜尋 key 對應的桶索引 int index = findBucket(hashMap, key); // 若找到鍵值對,則覆蓋 val 並返回 if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { free(hashMap->buckets[index]->val); hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(hashMap->buckets[index]->val, val); hashMap->buckets[index]->val[strlen(val)] = '\0'; return; } // 若鍵值對不存在,則新增該鍵值對 Pair *pair = (Pair *)malloc(sizeof(Pair)); pair->key = key; pair->val = (char *)malloc(sizeof(strlen(val) + 1)); strcpy(pair->val, val); pair->val[strlen(val)] = '\0'; hashMap->buckets[index] = pair; hashMap->size++; } /* 刪除操作 */ void removeItem(HashMapOpenAddressing *hashMap, int key) { // 搜尋 key 對應的桶索引 int index = findBucket(hashMap, key); // 若找到鍵值對,則用刪除標記覆蓋它 if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { Pair *pair = hashMap->buckets[index]; free(pair->val); free(pair); hashMap->buckets[index] = hashMap->TOMBSTONE; hashMap->size--; } } /* 擴容雜湊表 */ void extend(HashMapOpenAddressing *hashMap) { // 暫存原雜湊表 Pair **bucketsTmp = hashMap->buckets; int oldCapacity = hashMap->capacity; // 初始化擴容後的新雜湊表 hashMap->capacity *= hashMap->extendRatio; hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); hashMap->size = 0; // 將鍵值對從原雜湊表搬運至新雜湊表 for (int i = 0; i < oldCapacity; i++) { Pair *pair = bucketsTmp[i]; if (pair != NULL && pair != hashMap->TOMBSTONE) { put(hashMap, pair->key, pair->val); free(pair->val); free(pair); } } free(bucketsTmp); } /* 列印雜湊表 */ void print(HashMapOpenAddressing *hashMap) { for (int i = 0; i < hashMap->capacity; i++) { Pair *pair = hashMap->buckets[i]; if (pair == NULL) { printf("NULL\n"); } else if (pair == hashMap->TOMBSTONE) { printf("TOMBSTONE\n"); } else { printf("%d -> %s\n", pair->key, pair->val); } } } ``` === "Kotlin" ```kotlin title="hash_map_open_addressing.kt" /* 開放定址雜湊表 */ class HashMapOpenAddressing { private var size: Int // 鍵值對數量 private var capacity: Int // 雜湊表容量 private val loadThres: Double // 觸發擴容的負載因子閾值 private val extendRatio: Int // 擴容倍數 private var buckets: Array // 桶陣列 private val TOMBSTONE: Pair // 刪除標記 /* 建構子 */ init { size = 0 capacity = 4 loadThres = 2.0 / 3.0 extendRatio = 2 buckets = arrayOfNulls(capacity) TOMBSTONE = Pair(-1, "-1") } /* 雜湊函式 */ fun hashFunc(key: Int): Int { return key % capacity } /* 負載因子 */ fun loadFactor(): Double { return (size / capacity).toDouble() } /* 搜尋 key 對應的桶索引 */ fun findBucket(key: Int): Int { var index = hashFunc(key) var firstTombstone = -1 // 線性探查,當遇到空桶時跳出 while (buckets[index] != null) { // 若遇到 key ,返回對應的桶索引 if (buckets[index]?.key == key) { // 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index] buckets[index] = TOMBSTONE return firstTombstone // 返回移動後的桶索引 } return index // 返回桶索引 } // 記錄遇到的首個刪除標記 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index } // 計算桶索引,越過尾部則返回頭部 index = (index + 1) % capacity } // 若 key 不存在,則返回新增點的索引 return if (firstTombstone == -1) index else firstTombstone } /* 查詢操作 */ fun get(key: Int): String? { // 搜尋 key 對應的桶索引 val index = findBucket(key) // 若找到鍵值對,則返回對應 val if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index]?._val } // 若鍵值對不存在,則返回 null return null } /* 新增操作 */ fun put(key: Int, _val: String) { // 當負載因子超過閾值時,執行擴容 if (loadFactor() > loadThres) { extend() } // 搜尋 key 對應的桶索引 val index = findBucket(key) // 若找到鍵值對,則覆蓋 val 並返回 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index]!!._val = _val return } // 若鍵值對不存在,則新增該鍵值對 buckets[index] = Pair(key, _val) size++ } /* 刪除操作 */ fun remove(key: Int) { // 搜尋 key 對應的桶索引 val index = findBucket(key) // 若找到鍵值對,則用刪除標記覆蓋它 if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE size-- } } /* 擴容雜湊表 */ fun extend() { // 暫存原雜湊表 val bucketsTmp = buckets // 初始化擴容後的新雜湊表 capacity *= extendRatio buckets = arrayOfNulls(capacity) size = 0 // 將鍵值對從原雜湊表搬運至新雜湊表 for (pair in bucketsTmp) { if (pair != null && pair != TOMBSTONE) { put(pair.key, pair._val) } } } /* 列印雜湊表 */ fun print() { for (pair in buckets) { if (pair == null) { println("null") } else if (pair == TOMBSTONE) { println("TOMESTOME") } else { println("${pair.key} -> ${pair._val}") } } } } ``` === "Ruby" ```ruby title="hash_map_open_addressing.rb" ### 開放定址雜湊表 ### class HashMapOpenAddressing TOMBSTONE = Pair.new(-1, '-1') # 刪除標記 ### 建構子 ### def initialize @size = 0 # 鍵值對數量 @capacity = 4 # 雜湊表容量 @load_thres = 2.0 / 3.0 # 觸發擴容的負載因子閾值 @extend_ratio = 2 # 擴容倍數 @buckets = Array.new(@capacity) # 桶陣列 end ### 雜湊函式 ### def hash_func(key) key % @capacity end ### 負載因子 ### def load_factor @size / @capacity end ### 搜尋 key 對應的桶索引 ### def find_bucket(key) index = hash_func(key) first_tombstone = -1 # 線性探查,當遇到空桶時跳出 while !@buckets[index].nil? # 若遇到 key ,返回對應的桶索引 if @buckets[index].key == key # 若之前遇到了刪除標記,則將鍵值對移動至該索引處 if first_tombstone != -1 @buckets[first_tombstone] = @buckets[index] @buckets[index] = TOMBSTONE return first_tombstone # 返回移動後的桶索引 end return index # 返回桶索引 end # 記錄遇到的首個刪除標記 first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE # 計算桶索引,越過尾部則返回頭部 index = (index + 1) % @capacity end # 若 key 不存在,則返回新增點的索引 first_tombstone == -1 ? index : first_tombstone end ### 查詢操作 ### def get(key) # 搜尋 key 對應的桶索引 index = find_bucket(key) # 若找到鍵值對,則返回對應 val return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) # 若鍵值對不存在,則返回 nil nil end ### 新增操作 ### def put(key, val) # 當負載因子超過閾值時,執行擴容 extend if load_factor > @load_thres # 搜尋 key 對應的桶索引 index = find_bucket(key) # 若找到鍵值對,則覆蓋 val 開返回 unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index].val = val return end # 若鍵值對不存在,則新增該鍵值對 @buckets[index] = Pair.new(key, val) @size += 1 end ### 刪除操作 ### def remove(key) # 搜尋 key 對應的桶索引 index = find_bucket(key) # 若找到鍵值對,則用刪除標記覆蓋它 unless [nil, TOMBSTONE].include?(@buckets[index]) @buckets[index] = TOMBSTONE @size -= 1 end end ### 擴容雜湊表 ### def extend # 暫存原雜湊表 buckets_tmp = @buckets # 初始化擴容後的新雜湊表 @capacity *= @extend_ratio @buckets = Array.new(@capacity) @size = 0 # 將鍵值對從原雜湊表搬運至新雜湊表 for pair in buckets_tmp put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) end end ### 列印雜湊表 ### def print for pair in @buckets if pair.nil? puts "Nil" elsif pair == TOMBSTONE puts "TOMBSTONE" else puts "#{pair.key} -> #{pair.val}" end end end end ``` === "Zig" ```zig title="hash_map_open_addressing.zig" [class]{HashMapOpenAddressing}-[func]{} ``` ### 2.   平方探測 平方探測與線性探查類似,都是開放定址的常見策略之一。當發生衝突時,平方探測不是簡單地跳過一個固定的步數,而是跳過“探測次數的平方”的步數,即 $1, 4, 9, \dots$ 步。 平方探測主要具有以下優勢。 - 平方探測透過跳過探測次數平方的距離,試圖緩解線性探查的聚集效應。 - 平方探測會跳過更大的距離來尋找空位置,有助於資料分佈得更加均勻。 然而,平方探測並不是完美的。 - 仍然存在聚集現象,即某些位置比其他位置更容易被佔用。 - 由於平方的增長,平方探測可能不會探測整個雜湊表,這意味著即使雜湊表中有空桶,平方探測也可能無法訪問到它。 ### 3.   多次雜湊 顧名思義,多次雜湊方法使用多個雜湊函式 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ 進行探測。 - **插入元素**:若雜湊函式 $f_1(x)$ 出現衝突,則嘗試 $f_2(x)$ ,以此類推,直到找到空位後插入元素。 - **查詢元素**:在相同的雜湊函式順序下進行查詢,直到找到目標元素時返回;若遇到空位或已嘗試所有雜湊函式,說明雜湊表中不存在該元素,則返回 `None` 。 與線性探查相比,多次雜湊方法不易產生聚集,但多個雜湊函式會帶來額外的計算量。 !!! tip 請注意,開放定址(線性探查、平方探測和多次雜湊)雜湊表都存在“不能直接刪除元素”的問題。 ## 6.2.3   程式語言的選擇 各種程式語言採取了不同的雜湊表實現策略,下面舉幾個例子。 - Python 採用開放定址。字典 `dict` 使用偽隨機數進行探測。 - Java 採用鏈式位址。自 JDK 1.8 以來,當 `HashMap` 內陣列長度達到 64 且鏈結串列長度達到 8 時,鏈結串列會轉換為紅黑樹以提升查詢效能。 - Go 採用鏈式位址。Go 規定每個桶最多儲存 8 個鍵值對,超出容量則連線一個溢位桶;當溢位桶過多時,會執行一次特殊的等量擴容操作,以確保效能。