feat: Revised the book (#978)

* Sync recent changes to the revised Word.

* Revised the preface chapter

* Revised the introduction chapter

* Revised the computation complexity chapter

* Revised the chapter data structure

* Revised the chapter array and linked list

* Revised the chapter stack and queue

* Revised the chapter hashing

* Revised the chapter tree

* Revised the chapter heap

* Revised the chapter graph

* Revised the chapter searching

* Reivised the sorting chapter

* Revised the divide and conquer chapter

* Revised the chapter backtacking

* Revised the DP chapter

* Revised the greedy chapter

* Revised the appendix chapter

* Revised the preface chapter doubly

* Revised the figures
This commit is contained in:
Yudong Jin
2023-12-02 06:21:34 +08:00
committed by GitHub
parent b824d149cb
commit e720aa2d24
404 changed files with 1537 additions and 1558 deletions

View File

@@ -8,35 +8,35 @@
- 两个不同的 `key` 可能在经过哈希函数后得到相同的数组索引,导致查询结果出错,这种现象被称为哈希冲突。
- 哈希表容量越大,哈希冲突的概率就越低。因此可以通过扩容哈希表来缓解哈希冲突。与数组扩容类似,哈希表扩容操作的开销很大。
- 负载因子定义为哈希表中元素数量除以桶数量,反映了哈希冲突的严重程度,常用作触发哈希表扩容的条件。
- 链式地址通过将单个元素转化为链表,将所有冲突元素存储在同一个链表中。然而,链表过长会降低查询效率,可以进一步将链表转换为红黑树来提高效率。
- 链式地址通过将单个元素转化为链表,将所有冲突元素存储在同一个链表中。然而,链表过长会降低查询效率,可以通过进一步将链表转换为红黑树来提高效率。
- 开放寻址通过多次探测来处理哈希冲突。线性探测使用固定步长,缺点是不能删除元素,且容易产生聚集。多次哈希使用多个哈希函数进行探测,相较线性探测更不易产生聚集,但多个哈希函数增加了计算量。
- 不同编程语言采取了不同的哈希表实现。例如Java 的 `HashMap` 使用链式地址,而 Python 的 `Dict` 采用开放寻址。
- 在哈希表中,我们希望哈希算法具有确定性、高效率和均匀分布的特点。在密码学中,哈希算法还应该具备抗碰撞性和雪崩效应。
- 哈希算法通常采用大质数作为模数,以最大化地保证哈希值均匀分布,减少哈希冲突。
- 常见的哈希算法包括 MD5、SHA-1、SHA-2 和 SHA3 等。MD5 常用于校验文件完整性SHA-2 常用于安全应用与协议。
- 哈希算法通常采用大质数作为模数,以最大化地保证哈希值均匀分布,减少哈希冲突。
- 常见的哈希算法包括 MD5、SHA-1、SHA-2 和 SHA-3 等。MD5 常用于校验文件完整性SHA-2 常用于安全应用与协议。
- 编程语言通常会为数据类型提供内置哈希算法,用于计算哈希表中的桶索引。通常情况下,只有不可变对象是可哈希的。
### Q & A
!!! question "哈希表的时间复杂度为什么不是 $O(n)$ "
当哈希冲突比较严重时,哈希表的时间复杂度会退化至 $O(n)$ 。当哈希函数设计比较好、容量设置比较合理、冲突比较平均时,时间复杂度是 $O(1)$ 。我们使用编程语言内置的哈希表时,通常认为时间复杂度是 $O(1)$ 。
当哈希冲突比较严重时,哈希表的时间复杂度会退化至 $O(n)$ 。当哈希函数设计比较好、容量设置比较合理、冲突比较平均时,时间复杂度是 $O(1)$ 。我们使用编程语言内置的哈希表时,通常认为时间复杂度是 $O(1)$ 。
!!! question "为什么不使用哈希函数 $f(x) = x$ 呢?这样就不会有冲突了"
在 $f(x) = x$ 哈希函数下,每个元素对应唯一的桶索引,这与数组等价。然而,输入空间通常远大于输出空间(数组长度),因此哈希函数的最后一步往往是对数组长度取模。换句话说,哈希表的目标是将一个较大的状态空间映射到一个较小的空间,并提供 $O(1)$ 的查询效率。
!!! question "哈希表底层实现是数组、链表、二叉树,但为什么效率可以比们更高呢?"
!!! question "哈希表底层实现是数组、链表、二叉树,但为什么效率可以比们更高呢?"
首先,哈希表的时间效率变高,但空间效率变低了。哈希表有相当一部分内存未使用的,
首先,哈希表的时间效率变高,但空间效率变低了。哈希表有相当一部分内存未使用
其次,只是在特定使用场景下时间效率变高了。如果一个功能能够在相同的时间复杂度下使用数组或链表实现,那么通常比哈希表更快。这是因为哈希函数计算需要开销,时间复杂度的常数项更大。
最后,哈希表的时间复杂度可能发生劣化。例如在链式地址中,我们采取在链表或红黑树中执行查找操作,仍然有退化至 $O(n)$ 时间的风险。
!!! question "多次哈希有不能直接删除元素的缺陷吗?对于标记已删除的空间,这个空间还能再次使用吗?"
!!! question "多次哈希有不能直接删除元素的缺陷吗?标记已删除的空间还能再次使用吗?"
多次哈希是开放寻址的一种,开放寻址法都有不能直接删除元素的缺陷,需要通过标记删除。标记为已删除的空间可以再次使用。当将新元素插入哈希表,并且通过哈希函数找到了被标记为已删除的位置时,该位置可以被新元素使用。这样做既能保持哈希表的探测序列不变,又能保证哈希表的空间使用率。
多次哈希是开放寻址的一种,开放寻址法都有不能直接删除元素的缺陷,需要通过标记删除。标记为已删除的空间可以再次使用。当将新元素插入哈希表,并且通过哈希函数找到标记为已删除的位置时,该位置可以被新元素使用。这样做既能保持哈希表的探测序列不变,又能保证哈希表的空间使用率。
!!! question "为什么在线性探测中,查找元素的时候会出现哈希冲突呢?"
@@ -44,4 +44,4 @@
!!! question "为什么哈希表扩容能够缓解哈希冲突?"
哈希函数的最后一步往往是对数组长度 $n$ 取余,让输出值落在数组索引范围;在扩容后,数组长度 $n$ 发生变化,而 `key` 对应的索引也可能发生变化。原先落在同一个桶的多个 `key` ,在扩容后可能会被分配到多个桶中,从而实现哈希冲突的缓解。
哈希函数的最后一步往往是对数组长度 $n$ 取余,让输出值落在数组索引范围;在扩容后,数组长度 $n$ 发生变化,而 `key` 对应的索引也可能发生变化。原先落在同一个桶的多个 `key` ,在扩容后可能会被分配到多个桶中,从而实现哈希冲突的缓解。