This commit is contained in:
krahets
2023-08-19 22:07:27 +08:00
parent 71c7786f51
commit 2e27ad1680
99 changed files with 283 additions and 283 deletions
+5 -5
View File
@@ -2,7 +2,7 @@
comments: true
---
# 6.3.   哈希算法
# 6.3   哈希算法
在上两节中,我们了解了哈希表的工作原理和哈希冲突的处理方法。然而无论是开放寻址还是链地址法,**它们只能保证哈希表可以在发生冲突时正常工作,但无法减少哈希冲突的发生**。
@@ -22,7 +22,7 @@ index = hash(key) % capacity
这意味着,为了减小哈希冲突的发生概率,我们应当将注意力集中在哈希算法 `hash()` 的设计上。
## 6.3.1.   哈希算法的目标
## 6.3.1   哈希算法的目标
为了实现“既快又稳”的哈希表数据结构,哈希算法应包含以下特点:
@@ -42,7 +42,7 @@ index = hash(key) % capacity
请注意,**“均匀分布”与“抗碰撞性”是两个独立的概念**,满足均匀分布不一定满足抗碰撞性。例如,在随机输入 `key` 下,哈希函数 `key % 100` 可以产生均匀分布的输出。然而该哈希算法过于简单,所有后两位相等的 `key` 的输出都相同,因此我们可以很容易地从哈希值反推出可用的 `key` ,从而破解密码。
## 6.3.2.   哈希算法的设计
## 6.3.2   哈希算法的设计
哈希算法的设计是一个复杂且需要考虑许多因素的问题。然而对于简单场景,我们也能设计一些简单的哈希算法。以字符串哈希为例:
@@ -520,7 +520,7 @@ $$
总而言之,我们通常选取质数作为模数,并且这个质数最好足够大,以尽可能消除周期性模式,提升哈希算法的稳健性。
## 6.3.3.   常见哈希算法
## 6.3.3   常见哈希算法
不难发现,以上介绍的简单哈希算法都比较“脆弱”,远远没有达到哈希算法的设计目标。例如,由于加法和异或满足交换律,因此加法哈希和异或哈希无法区分内容相同但顺序不同的字符串,这可能会加剧哈希冲突,并引起一些安全问题。
@@ -544,7 +544,7 @@ $$
</div>
## 6.3.4. &nbsp; 数据结构的哈希值
## 6.3.4 &nbsp; 数据结构的哈希值
我们知道,哈希表的 `key` 可以是整数、小数或字符串等数据类型。编程语言通常会为这些数据类型提供内置的哈希算法,用于计算哈希表中的桶索引。以 Python 为例,我们可以调用 `hash()` 函数来计算各种数据类型的哈希值,包括:
+4 -4
View File
@@ -2,7 +2,7 @@
comments: true
---
# 6.2. &nbsp; 哈希冲突
# 6.2 &nbsp; 哈希冲突
上节提到,**通常情况下哈希函数的输入空间远大于输出空间**,因此理论上哈希冲突是不可避免的。比如,输入空间为全体整数,输出空间为数组容量大小,则必然有多个整数映射至同一数组索引。
@@ -13,7 +13,7 @@ comments: true
哈希表的结构改良方法主要包括链式地址和开放寻址。
## 6.2.1. &nbsp; 链式地址
## 6.2.1 &nbsp; 链式地址
在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 Separate Chaining」将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。
@@ -1152,7 +1152,7 @@ comments: true
当链表很长时,查询效率 $O(n)$ 很差,**此时可以将链表转换为「AVL 树」或「红黑树」**,从而将查询操作的时间复杂度优化至 $O(\log n)$ 。
## 6.2.2. &nbsp; 开放寻址
## 6.2.2 &nbsp; 开放寻址
「开放寻址 Open Addressing」不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测、多次哈希等。
@@ -2448,7 +2448,7 @@ comments: true
与线性探测相比,多次哈希方法不易产生聚集,但多个哈希函数会增加额外的计算量。
## 6.2.3. &nbsp; 编程语言的选择
## 6.2.3 &nbsp; 编程语言的选择
Java 采用链式地址。自 JDK 1.8 以来,当 HashMap 内数组长度达到 64 且链表长度达到 8 时,链表会被转换为红黑树以提升查找性能。
+4 -4
View File
@@ -2,7 +2,7 @@
comments: true
---
# 6.1. &nbsp; 哈希表
# 6.1 &nbsp; 哈希表
散列表,又称「哈希表 Hash Table」,其通过建立键 `key` 与值 `value` 之间的映射,实现高效的元素查询。具体而言,我们向哈希表输入一个键 `key` ,则可以在 $O(1)$ 时间内获取对应的值 `value`
@@ -31,7 +31,7 @@ comments: true
观察发现,**在哈希表中进行增删查改的时间复杂度都是 $O(1)$** ,非常高效。
## 6.1.1. &nbsp; 哈希表常用操作
## 6.1.1 &nbsp; 哈希表常用操作
哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等。
@@ -444,7 +444,7 @@ comments: true
```
## 6.1.2. &nbsp; 哈希表简单实现
## 6.1.2 &nbsp; 哈希表简单实现
我们先考虑最简单的情况,**仅用一个数组来实现哈希表**。在哈希表中,我们将数组中的每个空位称为「桶 Bucket」,每个桶可存储一个键值对。因此,查询操作就是找到 `key` 对应的桶,并在桶中获取 `value` 。
@@ -1487,7 +1487,7 @@ index = hash(key) % capacity
}
```
## 6.1.3. &nbsp; 哈希冲突与扩容
## 6.1.3 &nbsp; 哈希冲突与扩容
本质上看,哈希函数的作用是将所有 `key` 构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,**理论上一定存在“多个输入对应相同输出”的情况**。
+1 -1
View File
@@ -3,7 +3,7 @@ comments: true
icon: material/table-search
---
# 6. &nbsp; 散列表
# 第 6 章 &nbsp; 散列表
<div class="center-table" markdown>
+2 -2
View File
@@ -2,7 +2,7 @@
comments: true
---
# 6.4. &nbsp; 小结
# 6.4 &nbsp; 小结
- 输入 `key` ,哈希表能够在 $O(1)$ 时间内查询到 `value` ,效率非常高。
- 常见的哈希表操作包括查询、添加键值对、删除键值对和遍历哈希表等。
@@ -18,7 +18,7 @@ comments: true
- 常见的哈希算法包括 MD5, SHA-1, SHA-2, SHA3 等。MD5 常用于校验文件完整性,SHA-2 常用于安全应用与协议。
- 编程语言通常会为数据类型提供内置哈希算法,用于计算哈希表中的桶索引。通常情况下,只有不可变对象是可哈希的。
## 6.4.1. &nbsp; Q & A
## 6.4.1 &nbsp; Q & A
!!! question "哈希表的时间复杂度为什么不是 $O(n)$ ?"