This commit is contained in:
krahets
2024-04-03 04:41:27 +08:00
parent 20f79f5f32
commit 8591529021
47 changed files with 136 additions and 135 deletions

View File

@ -15,7 +15,7 @@ comments: true
## 6.2.1   链式地址
在原始哈希表中,每个桶仅能存储一个键值对。链式地址 separate chaining将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。图 6-5 展示了一个链式地址哈希表的例子。
在原始哈希表中,每个桶仅能存储一个键值对。<u>链式地址separate chaining</u>将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。图 6-5 展示了一个链式地址哈希表的例子。
![链式地址哈希表](hash_collision.assets/hash_table_chaining.png){ class="animation-figure" }
@ -1447,7 +1447,7 @@ comments: true
## 6.2.2 &nbsp; 开放寻址
开放寻址 open addressing不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测和多次哈希等。
<u>开放寻址open addressing</u>不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测和多次哈希等。
下面以线性探测为例,介绍开放寻址哈希表的工作机制。
@ -1472,7 +1472,7 @@ comments: true
<p align="center"> 图 6-7 &nbsp; 在开放寻址中删除元素导致的查询问题 </p>
为了解决该问题,我们可以采用懒删除 lazy deletion机制:它不直接从哈希表中移除元素,**而是利用一个常量 `TOMBSTONE` 来标记这个桶**。在该机制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置键值对。但不同的是,线性探测到 `TOMBSTONE` 时应该继续遍历,因为其之下可能还存在键值对。
为了解决该问题,我们可以采用<u>懒删除lazy deletion</u>机制:它不直接从哈希表中移除元素,**而是利用一个常量 `TOMBSTONE` 来标记这个桶**。在该机制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置键值对。但不同的是,线性探测到 `TOMBSTONE` 时应该继续遍历,因为其之下可能还存在键值对。
然而,**懒删除可能会加速哈希表的性能退化**。这是因为每次删除操作都会产生一个删除标记,随着 `TOMBSTONE` 的增加,搜索时间也会增加,因为线性探测可能需要跳过多个 `TOMBSTONE` 才能找到目标元素。

View File

@ -4,7 +4,7 @@ comments: true
# 6.1 &nbsp; 哈希表
哈希表 hash table」,又称「散列表」,它通过建立键 `key` 与值 `value` 之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键 `key` ,则可以在 $O(1)$ 时间内获取对应的值 `value`
<u>哈希表hash table</u>,又称<u>散列表</u>,它通过建立键 `key` 与值 `value` 之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键 `key` ,则可以在 $O(1)$ 时间内获取对应的值 `value`
如图 6-1 所示,给定 $n$ 个学生,每个学生都有“姓名”和“学号”两项数据。假如我们希望实现“输入一个学号,返回对应的姓名”的查询功能,则可以采用图 6-1 所示的哈希表来实现。
@ -539,9 +539,9 @@ comments: true
## 6.1.2 &nbsp; 哈希表简单实现
我们先考虑最简单的情况,**仅用一个数组来实现哈希表**。在哈希表中,我们将数组中的每个空位称为「桶 bucket,每个桶可存储一个键值对。因此,查询操作就是找到 `key` 对应的桶,并在桶中获取 `value` 。
我们先考虑最简单的情况,**仅用一个数组来实现哈希表**。在哈希表中,我们将数组中的每个空位称为<u>桶(bucket</u>,每个桶可存储一个键值对。因此,查询操作就是找到 `key` 对应的桶,并在桶中获取 `value` 。
那么,如何基于 `key` 定位对应的桶呢?这是通过哈希函数 hash function实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有 `key` ,输出空间是所有桶(数组索引)。换句话说,输入一个 `key` **我们可以通过哈希函数得到该 `key` 对应的键值对在数组中的存储位置**。
那么,如何基于 `key` 定位对应的桶呢?这是通过<u>哈希函数hash function</u>实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有 `key` ,输出空间是所有桶(数组索引)。换句话说,输入一个 `key` **我们可以通过哈希函数得到该 `key` 对应的键值对在数组中的存储位置**。
输入一个 `key` ,哈希函数的计算过程分为以下两步。
@ -1871,7 +1871,7 @@ index = hash(key) % capacity
20336 % 100 = 36
```
如图 6-3 所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为哈希冲突 hash collision
如图 6-3 所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为<u>哈希冲突hash collision</u>
![哈希冲突示例](hash_map.assets/hash_collision.png){ class="animation-figure" }
@ -1887,4 +1887,4 @@ index = hash(key) % capacity
类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时;并且由于哈希表容量 `capacity` 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步增加了扩容过程的计算开销。为此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。
负载因子 load factor是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,**也常作为哈希表扩容的触发条件**。例如在 Java 中,当负载因子超过 $0.75$ 时,系统会将哈希表扩容至原先的 $2$ 倍。
<u>负载因子load factor</u>是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,**也常作为哈希表扩容的触发条件**。例如在 Java 中,当负载因子超过 $0.75$ 时,系统会将哈希表扩容至原先的 $2$ 倍。