From a46b4829518779b91581971dda5014926cde2da3 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Thu, 21 Sep 2023 04:43:15 -0500 Subject: [PATCH] Refactor the hash_map_open_addressing implementation with lazy reallocation. (#776) --- .../hash_map_open_addressing.cpp | 177 ++++++++++-------- .../hash_map_open_addressing.py | 96 +++++----- .../hash_table_open_addressing_deletion.png | Bin 0 -> 59604 bytes docs/chapter_hashing/hash_collision.md | 62 ++++-- 4 files changed, 190 insertions(+), 145 deletions(-) create mode 100644 docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png diff --git a/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp b/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp index eac382f32..b331fffb2 100644 --- a/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp +++ b/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp @@ -6,26 +6,28 @@ #include "./array_hash_map.cpp" -/* 开放寻址哈希表 */ class HashMapOpenAddressing { private: - int size; // 键值对数量 - int capacity; // 哈希表容量 - double loadThres; // 触发扩容的负载因子阈值 - int extendRatio; // 扩容倍数 - vector buckets; // 桶数组 - Pair *removed; // 删除标记 + int size; // 键值对数量 + int capacity = 4; // 哈希表容量 + const double loadThres = 2.0 / 3; // 触发扩容的负载因子阈值 + const int extendRatio = 2; // 扩容倍数 + vector buckets; // 桶数组 + Pair *TOMBSTONE = new Pair(-1, "-1"); // 删除标记 public: /* 构造方法 */ - HashMapOpenAddressing() { - // 构造方法 - size = 0; - capacity = 4; - loadThres = 2.0 / 3.0; - extendRatio = 2; - buckets = vector(capacity, nullptr); - removed = new Pair(-1, "-1"); + HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { + } + + /* 析构方法 */ + ~HashMapOpenAddressing() { + for (Pair *pair : buckets) { + if (pair != nullptr && pair != TOMBSTONE) { + delete pair; + } + } + delete TOMBSTONE; } /* 哈希函数 */ @@ -35,67 +37,75 @@ class HashMapOpenAddressing { /* 负载因子 */ double loadFactor() { - return static_cast(size) / capacity; + return (double)size / capacity; } - /* 查询操作 */ - string get(int key) { + /* 搜索 key 对应的桶索引 */ + int findBucket(int key) { int index = hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (int i = 0; i < capacity; i++) { + 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; + } // 计算桶索引,越过尾部返回头部 - int j = (index + i) % capacity; - // 若遇到空桶,说明无此 key ,则返回 nullptr - if (buckets[j] == nullptr) - return nullptr; - // 若遇到指定 key ,则返回对应 val - if (buckets[j]->key == key && buckets[j] != removed) - return buckets[j]->val; + 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 nullptr; + // 若键值对不存在,则返回空字符串 + return ""; } /* 添加操作 */ void put(int key, string val) { // 当负载因子超过阈值时,执行扩容 - if (loadFactor() > loadThres) + if (loadFactor() > loadThres) { extend(); - int index = hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (int i = 0; i < capacity; i++) { - // 计算桶索引,越过尾部返回头部 - int j = (index + i) % capacity; - // 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 - if (buckets[j] == nullptr || buckets[j] == removed) { - buckets[j] = new Pair(key, val); - size += 1; - return; - } - // 若遇到指定 key ,则更新对应 val - if (buckets[j]->key == key) { - buckets[j]->val = val; - return; - } } + // 搜索 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) { - int index = hashFunc(key); - // 线性探测,从 index 开始向后遍历 - for (int i = 0; i < capacity; i++) { - // 计算桶索引,越过尾部返回头部 - int j = (index + i) % capacity; - // 若遇到空桶,说明无此 key ,则直接返回 - if (buckets[j] == nullptr) - return; - // 若遇到指定 key ,则标记删除并返回 - if (buckets[j]->key == key) { - delete buckets[j]; // 释放内存 - buckets[j] = removed; - size -= 1; - return; - } + // 搜索 key 对应的桶索引 + int index = findBucket(key); + // 若找到键值对,则用删除标记覆盖它 + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + delete buckets[index]; + buckets[index] = TOMBSTONE; + size--; } } @@ -109,19 +119,22 @@ class HashMapOpenAddressing { size = 0; // 将键值对从原哈希表搬运至新哈希表 for (Pair *pair : bucketsTmp) { - if (pair != nullptr && pair != removed) { + if (pair != nullptr && pair != TOMBSTONE) { put(pair->key, pair->val); + delete pair; } } } /* 打印哈希表 */ void print() { - for (auto &pair : buckets) { - if (pair != nullptr) { - cout << pair->key << " -> " << pair->val << endl; - } else { + for (Pair *pair : buckets) { + if (pair == nullptr) { cout << "nullptr" << endl; + } else if (pair == TOMBSTONE) { + cout << "TOMBSTONE" << endl; + } else { + cout << pair->key << " -> " << pair->val << endl; } } } @@ -129,29 +142,29 @@ class HashMapOpenAddressing { /* Driver Code */ int main() { - /* 初始化哈希表 */ - HashMapOpenAddressing map = HashMapOpenAddressing(); + // 初始化哈希表 + HashMapOpenAddressing hashmap; - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - map.put(12836, "小哈"); - map.put(15937, "小啰"); - map.put(16750, "小算"); - map.put(13276, "小法"); - map.put(10583, "小鸭"); + // 添加操作 + // 在哈希表中添加键值对 (key, val) + hashmap.put(12836, "小哈"); + hashmap.put(15937, "小啰"); + hashmap.put(16750, "小算"); + hashmap.put(13276, "小法"); + hashmap.put(10583, "小鸭"); cout << "\n添加完成后,哈希表为\nKey -> Value" << endl; - map.print(); + hashmap.print(); - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - string name = map.get(13276); + // 查询操作 + // 向哈希表输入键 key ,得到值 val + string name = hashmap.get(13276); cout << "\n输入学号 13276 ,查询到姓名 " << name << endl; - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - map.remove(16750); + // 删除操作 + // 在哈希表中删除键值对 (key, val) + hashmap.remove(16750); cout << "\n删除 16750 后,哈希表为\nKey -> Value" << endl; - map.print(); + hashmap.print(); return 0; } diff --git a/codes/python/chapter_hashing/hash_map_open_addressing.py b/codes/python/chapter_hashing/hash_map_open_addressing.py index 7dbc6015c..9ecefe863 100644 --- a/codes/python/chapter_hashing/hash_map_open_addressing.py +++ b/codes/python/chapter_hashing/hash_map_open_addressing.py @@ -20,7 +20,7 @@ class HashMapOpenAddressing: self.load_thres = 2 / 3 # 触发扩容的负载因子阈值 self.extend_ratio = 2 # 扩容倍数 self.buckets: list[Pair | None] = [None] * self.capacity # 桶数组 - self.removed = Pair(-1, "-1") # 删除标记 + self.TOMBSTONE = Pair(-1, "-1") # 删除标记 def hash_func(self, key: int) -> int: """哈希函数""" @@ -30,55 +30,61 @@ class HashMapOpenAddressing: """负载因子""" return self.size / self.capacity - def get(self, key: int) -> str: - """查询操作""" + def find_bucket(self, key: int) -> int: + """搜索 key 对应的桶索引""" index = self.hash_func(key) - # 线性探测,从 index 开始向后遍历 - for i in range(self.capacity): + 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 # 计算桶索引,越过尾部返回头部 - j = (index + i) % self.capacity - # 若遇到空桶,说明无此 key ,则返回 None - if self.buckets[j] is None: - return None - # 若遇到指定 key ,则返回对应 val - if self.buckets[j].key == key and self.buckets[j] != self.removed: - return self.buckets[j].val + 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() - index = self.hash_func(key) - # 线性探测,从 index 开始向后遍历 - for i in range(self.capacity): - # 计算桶索引,越过尾部返回头部 - j = (index + i) % self.capacity - # 若遇到空桶、或带有删除标记的桶,则将键值对放入该桶 - if self.buckets[j] in [None, self.removed]: - self.buckets[j] = Pair(key, val) - self.size += 1 - return - # 若遇到指定 key ,则更新对应 val - if self.buckets[j].key == key: - self.buckets[j].val = val - return + # 搜索 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): """删除操作""" - index = self.hash_func(key) - # 线性探测,从 index 开始向后遍历 - for i in range(self.capacity): - # 计算桶索引,越过尾部返回头部 - j = (index + i) % self.capacity - # 若遇到空桶,说明无此 key ,则直接返回 - if self.buckets[j] is None: - return - # 若遇到指定 key ,则标记删除并返回 - if self.buckets[j].key == key: - self.buckets[j] = self.removed - self.size -= 1 - return + # 搜索 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): """扩容哈希表""" @@ -90,21 +96,23 @@ class HashMapOpenAddressing: self.size = 0 # 将键值对从原哈希表搬运至新哈希表 for pair in buckets_tmp: - if pair not in [None, self.removed]: + if pair not in [None, self.TOMBSTONE]: self.put(pair.key, pair.val) def print(self): """打印哈希表""" for pair in self.buckets: - if pair is not None: - print(pair.key, "->", pair.val) - else: + if pair is None: print("None") + elif pair is self.TOMBSTONE: + print("TOMBSTONE") + else: + print(pair.key, "->", pair.val) """Driver Code""" if __name__ == "__main__": - # 测试代码 + # 初始化哈希表 hashmap = HashMapOpenAddressing() # 添加操作 diff --git a/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png b/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png new file mode 100644 index 0000000000000000000000000000000000000000..3e387e167b9dc6431de9e8200769e333f02166be GIT binary patch literal 59604 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 QIPgzaQc0pn-1zDL15^~j-T(jq literal 0 HcmV?d00001 diff --git a/docs/chapter_hashing/hash_collision.md b/docs/chapter_hashing/hash_collision.md index dd3522620..3d076926f 100644 --- a/docs/chapter_hashing/hash_collision.md +++ b/docs/chapter_hashing/hash_collision.md @@ -1,6 +1,6 @@ # 哈希冲突 -上节提到,**通常情况下哈希函数的输入空间远大于输出空间**,因此理论上哈希冲突是不可避免的。比如,输入空间为全体整数,输出空间为数组容量大小,则必然有多个整数映射至同一数组索引。 +上节提到,**通常情况下哈希函数的输入空间远大于输出空间**,因此理论上哈希冲突是不可避免的。比如,输入空间为全体整数,输出空间为数组容量大小,则必然有多个整数映射至同一桶索引。 哈希冲突会导致查询结果错误,严重影响哈希表的可用性。为解决该问题,我们可以每当遇到哈希冲突时就进行哈希表扩容,直至冲突消失为止。此方法简单粗暴且有效,但效率太低,因为哈希表扩容需要进行大量的数据搬运与哈希值计算。为了提升效率,我们可以采用以下策略。 @@ -15,9 +15,9 @@ ![链式地址哈希表](hash_collision.assets/hash_table_chaining.png) -哈希表在链式地址下的操作方法发生了一些变化。 +基于链式地址实现的哈希表的操作方法发生了以下变化。 -- **查询元素**:输入 `key` ,经过哈希函数得到数组索引,即可访问链表头节点,然后遍历链表并对比 `key` 以查找目标键值对。 +- **查询元素**:输入 `key` ,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比 `key` 以查找目标键值对。 - **添加元素**:先通过哈希函数访问链表头节点,然后将节点(即键值对)添加到链表中。 - **删除元素**:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点,并将其删除。 @@ -109,26 +109,32 @@ 「开放寻址 open addressing」不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测、多次哈希等。 +下面将主要以线性探测为例,介绍开放寻址哈希表的工作机制与代码实现。 + ### 线性探测 线性探测采用固定步长的线性搜索来进行探测,其操作方法与普通哈希表有所不同。 -- **插入元素**:通过哈希函数计算数组索引,若发现桶内已有元素,则从冲突位置向后线性遍历(步长通常为 $1$ ),直至找到空位,将元素插入其中。 -- **查找元素**:若发现哈希冲突,则使用相同步长向后线性遍历,直到找到对应元素,返回 `value` 即可;如果遇到空位,说明目标键值对不在哈希表中,返回 $\text{None}$ 。 +- **插入元素**:通过哈希函数计算桶索引,若发现桶内已有元素,则从冲突位置向后线性遍历(步长通常为 $1$ ),直至找到空桶,将元素插入其中。 +- **查找元素**:若发现哈希冲突,则使用相同步长向后线性遍历,直到找到对应元素,返回 `value` 即可;如果遇到空桶,说明目标元素不在哈希表中,返回 $\text{None}$ 。 -下图展示了一个在开放寻址(线性探测)下工作的哈希表。 +下图展示了开放寻址(线性探测)哈希表的键值对分布。根据此哈希函数,最后两位相同的 `key` 都会被映射到相同的桶。而通过线性探测,它们被依次存储在该桶以及之下的桶中。 ![开放寻址和线性探测](hash_collision.assets/hash_table_linear_probing.png) -然而,线性探测存在以下缺陷。 +然而,**线性探测容易产生“聚集现象”**。具体来说,数组中连续被占用的位置越长,这些连续位置发生哈希冲突的可能性越大,从而进一步促使该位置的聚堆生长,形成恶性循环,最终导致增删查改操作效率劣化。 + +值得注意的是,**我们不能在开放寻址哈希表中直接删除元素**。这是因为删除元素会在数组内产生一个空桶 $\text{None}$ ,而当查询元素时,线性探测到该空桶就会返回,因此在该空桶之下的元素都无法再被访问到,程序可能误判这些元素不存在。 + +![在开放寻址中删除元素导致的查询问题](hash_collision.assets/hash_table_open_addressing_deletion.png) + +为了解决该问题,我们可以采用「懒删除 lazy deletion」机制:它不直接从哈希表中移除元素,**而是利用一个常量 `TOMBSTONE` 来标记这个桶**。在该机制下,$\text{None}$ 和 `TOMBSTONE` 都代表空桶,都可以放置键值对。但不同的是,线性探测到 `TOMBSTONE` 时应该继续遍历,因为其之下可能还存在键值对。 -- **不能直接删除元素**。删除元素会在数组内产生一个空位,当查找该空位之后的元素时,该空位可能导致程序误判元素不存在。为此,通常需要借助一个标志位来标记已删除元素。 -- **容易产生聚集**。数组内连续被占用位置越长,这些连续位置发生哈希冲突的可能性越大,进一步促使这一位置的聚堆生长,形成恶性循环,最终导致增删查改操作效率劣化。 +然而,**懒删除可能会加速哈希表的性能退化**。这是因为每次删除操作都会产生一个删除标记,随着 `TOMBSTONE` 的增加,搜索时间也会增加,因为线性探测可能需要跳过多个 `TOMBSTONE` 才能找到目标元素。 -以下代码实现了一个简单的开放寻址(线性探测)哈希表。 +为此,考虑在线性探测中记录遇到的首个 `TOMBSTONE` 的索引,并将搜索到的目标元素与该 `TOMBSTONE` 交换位置。这样做的好处是当每次查询或添加元素时,元素会被移动至距离理想位置(探测起始点)更近的桶,从而优化查询效率。 -- 我们使用一个固定的键值对实例 `removed` 来标记已删除元素。也就是说,当一个桶内的元素为 $\text{None}$ 或 `removed` 时,说明这个桶是空的,可用于放置键值对。 -- 在线性探测时,我们从当前索引 `index` 向后遍历;而当越过数组尾部时,需要回到头部继续遍历。 +以下代码实现了一个包含懒删除的开放寻址(线性探测)哈希表。为了更加充分地使用哈希表的空间,我们将哈希表表看作是一个“环形数组”,当越过数组尾部时,回到头部继续遍历。 === "Python" @@ -202,19 +208,37 @@ [class]{HashMapOpenAddressing}-[func]{} ``` +### 平方探测 + +平方探测与线性探测类似,都是开放寻址的常见策略之一。当发生冲突时,平方探测不是简单地跳过一个固定的步数,而是跳过“探测次数的平方”的步数,即 $1, 4, 9, \dots$ 步。 + +平方探测通主要具有以下优势。 + +- 平方探测通过跳过平方的距离,试图缓解线性探测的聚集效应。 +- 平方探测会跳过更大的距离来寻找空位置,有助于数据分布得更加均匀。 + +然而,平方探测也并不是完美的。 + +- 仍然存在聚集现象,即某些位置比其他位置更容易被占用。 +- 由于平方的增长,平方探测可能不会探测整个哈希表,这意味着即使哈希表中有空桶,平方探测也可能无法访问到它。 + ### 多次哈希 -顾名思义,多次哈希方法是使用多个哈希函数 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ 进行探测。 +多次哈希使用多个哈希函数 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ 进行探测。 -- **插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推,直到找到空位后插入元素。 -- **查找元素**:在相同的哈希函数顺序下进行查找,直到找到目标元素时返回;或遇到空位或已尝试所有哈希函数,说明哈希表中不存在该元素,则返回 $\text{None}$ 。 +- **插入元素**:若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推,直到找到空桶后插入元素。 +- **查找元素**:在相同的哈希函数顺序下进行查找,直到找到目标元素时返回;或当遇到空桶或已尝试所有哈希函数,说明哈希表中不存在该元素,则返回 $\text{None}$ 。 与线性探测相比,多次哈希方法不易产生聚集,但多个哈希函数会增加额外的计算量。 -## 编程语言的选择 +!!! tip + + 请注意,开放寻址(线性探测、平方探测和多次哈希)哈希表都存在“不能直接删除元素”的问题。 -Java 采用链式地址。自 JDK 1.8 以来,当 HashMap 内数组长度达到 64 且链表长度达到 8 时,链表会被转换为红黑树以提升查找性能。 +## 编程语言的选择 -Python 采用开放寻址。字典 dict 使用伪随机数进行探测。 +各个编程语言采取了不同的哈希表实现策略,以下举几个例子。 -Golang 采用链式地址。Go 规定每个桶最多存储 8 个键值对,超出容量则连接一个溢出桶;当溢出桶过多时,会执行一次特殊的等量扩容操作,以确保性能。 +- Java 采用链式地址。自 JDK 1.8 以来,当 HashMap 内数组长度达到 64 且链表长度达到 8 时,链表会被转换为红黑树以提升查找性能。 +- Python 采用开放寻址。字典 dict 使用伪随机数进行探测。 +- Golang 采用链式地址。Go 规定每个桶最多存储 8 个键值对,超出容量则连接一个溢出桶。当溢出桶过多时,会执行一次特殊的等量扩容操作,以确保性能。