From 3902ccbfc7b09af60f80f1ff31321863f55af46e Mon Sep 17 00:00:00 2001 From: krahets Date: Sun, 25 Jun 2023 21:11:24 +0800 Subject: [PATCH] Fine-tune the docs. --- docs/chapter_computational_complexity/summary.md | 2 ++ docs/chapter_hashing/hash_map.md | 2 +- docs/chapter_hashing/summary.md | 6 +++--- docs/chapter_heap/summary.md | 3 ++- docs/chapter_tree/binary_search_tree.md | 9 ++++----- docs/chapter_tree/summary.md | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/chapter_computational_complexity/summary.md b/docs/chapter_computational_complexity/summary.md index c5dce824b..20ea33f85 100644 --- a/docs/chapter_computational_complexity/summary.md +++ b/docs/chapter_computational_complexity/summary.md @@ -34,6 +34,8 @@ 函数(function)可以独立被执行,所有参数都以显式传递。 方法(method)与一个对象关联,方法被隐式传递给调用它的对象,方法能够对类的实例中包含的数据进行操作。 + 因此,C 和 Go 只有函数,Java 和 C# 只有方法,在 C++, Python 中取决于它是否属于一个类。 + !!! question "图片“空间复杂度的常见类型”反映的是否是占用空间的绝对大小?" 不是,该图片展示的是空间复杂度(即增长趋势),而不是占用空间的绝对大小。每个曲线都包含一个常数项,用来把所有曲线的取值范围压缩到一个视觉舒适的范围内。 diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index 91a494300..00257ea54 100755 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -538,7 +538,7 @@ index = hash(key) % capacity ## 哈希冲突与扩容 -本质上看,哈希函数的作用是黄输入空间(`key` 范围)映射到输出空间(数组索引范围),而输入空间往往远大于输出空间。因此,**理论上一定存在“多个输入对应相同输出”的情况**。 +本质上看,哈希函数的作用是将输入空间(`key` 范围)映射到输出空间(数组索引范围),而输入空间往往远大于输出空间。因此,**理论上一定存在“多个输入对应相同输出”的情况**。 对于上述示例中的哈希函数,当输入的 `key` 后两位相同时,哈希函数的输出结果也相同。例如,查询学号为 12836 和 20336 的两个学生时,我们得到: diff --git a/docs/chapter_hashing/summary.md b/docs/chapter_hashing/summary.md index 34cff34b8..ea4885e9e 100644 --- a/docs/chapter_hashing/summary.md +++ b/docs/chapter_hashing/summary.md @@ -18,14 +18,14 @@ !!! 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 "哈希表底层实现是数组、链表、二叉树,但为什么效率可以比他们更高呢?" - + 首先,哈希表的时间效率变高,但空间效率变低了。哈希表有相当一部分的内存是未使用的, 其次,只是在特定使用场景下时间效率变高了。如果一个功能能够在相同的时间复杂度下使用数组或链表实现,那么通常比哈希表更快。这是因为哈希函数计算需要开销,时间复杂度的常数项更大。 @@ -33,7 +33,7 @@ 最后,哈希表的时间复杂度可能发生劣化。例如在链式地址中,我们采取在链表或红黑树中执行查找操作,仍然有退化至 $O(n)$ 时间的风险。 !!! question "多次哈希有不能直接删除元素的缺陷吗?对于标记已删除的空间,这个空间还能再次使用吗?" - + 多次哈希是开放寻址的一种,开放寻址法都有不能直接删除元素的缺陷,需要通过标记删除。被标记为已删除的空间是可以再次被使用的。当将新元素插入哈希表,并且通过哈希函数找到了被标记为已删除的位置时,该位置可以被新的元素使用。这样做既能保持哈希表的探测序列不变,又能保证哈希表的空间使用率。 !!! question "为什么在线性探测中,查找元素的时候会出现哈希冲突呢?" diff --git a/docs/chapter_heap/summary.md b/docs/chapter_heap/summary.md index 4a6f46f18..bf6425ddd 100644 --- a/docs/chapter_heap/summary.md +++ b/docs/chapter_heap/summary.md @@ -6,9 +6,10 @@ - 完全二叉树非常适合用数组表示,因此我们通常使用数组来存储堆。 - 堆化操作用于维护堆的性质,在入堆和出堆操作中都会用到。 - 输入 $n$ 个元素并建堆的时间复杂度可以优化至 $O(n)$ ,非常高效。 +- Top-K 是一个经典算法问题,可以使用堆数据结构高效解决,时间复杂度为 $O(n \log k)$ 。 ## Q & A !!! question "数据结构的“堆”与内存管理的“堆”是同一个概念吗?" - + 两者不是同一个概念,只是碰巧都叫堆。计算机系统内存中的堆是动态内存分配的一部分,程序在运行时可以使用它来存储数据。程序可以请求一定量的堆内存,用于存储如对象和数组等复杂结构。当这些数据不再需要时,程序需要释放这些内存,以防止内存泄露。相较于栈内存,堆内存的管理和使用需要更谨慎,不恰当的使用可能会导致内存泄露和野指针等问题。 diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index 8f760a5b0..3e4421c67 100755 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -182,19 +182,18 @@ 与插入节点类似,我们需要在删除操作后维持二叉搜索树的“左子树 < 根节点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除节点。接下来,根据待删除节点的子节点数量,删除操作需分为三种情况: -当待删除节点的子节点数量 $= 0$ 时,表示待删除节点是叶节点,可以直接删除。 +当待删除节点的度为 $0$ 时,表示待删除节点是叶节点,可以直接删除。 ![在二叉搜索树中删除节点(度为 0)](binary_search_tree.assets/bst_remove_case1.png) -当待删除节点的子节点数量 $= 1$ 时,将待删除节点替换为其子节点即可。 +当待删除节点的度为 $1$ 时,将待删除节点替换为其子节点即可。 ![在二叉搜索树中删除节点(度为 1)](binary_search_tree.assets/bst_remove_case2.png) -当待删除节点的子节点数量 $= 2$ 时,删除操作分为三步: +当待删除节点的度为 $2$ 时,我们无法直接删除它,而需要使用一个节点替换该节点。由于要保持二叉搜索树“左 $<$ 根 $<$ 右”的性质,因此这个节点可以是右子树的最小节点或左子树的最大节点。假设我们选择右子树的最小节点(或者称为中序遍历的下个节点),则删除操作为: 1. 找到待删除节点在“中序遍历序列”中的下一个节点,记为 `tmp` ; -2. 在树中递归删除节点 `tmp` ; -3. 用 `tmp` 的值覆盖待删除节点的值; +2. 将 `tmp` 的值覆盖待删除节点的值,并在树中递归删除节点 `tmp` ; === "<1>" ![二叉搜索树删除节点示例](binary_search_tree.assets/bst_remove_case3_step1.png) diff --git a/docs/chapter_tree/summary.md b/docs/chapter_tree/summary.md index 344ad456f..69492e56f 100644 --- a/docs/chapter_tree/summary.md +++ b/docs/chapter_tree/summary.md @@ -19,7 +19,7 @@ 是的,因为高度和深度通常定义为“走过边的数量”。 !!! question "二叉树中的插入与删除一般都是由一套操作配合完成的,这里的“一套操作”指什么呢?可以理解为资源的子节点的资源释放吗?" - + 拿二叉搜索树来举例,删除节点操作要分为三种情况处理,其中每种情况都需要进行多个步骤的节点操作。 !!! question "为什么 DFS 遍历二叉树有前、中、后三种顺序,分别有什么用呢?"