Fine-tune the docs.

pull/568/head
krahets 1 year ago
parent 3b3841ba36
commit 3902ccbfc7

@ -34,6 +34,8 @@
函数function可以独立被执行所有参数都以显式传递。
方法method与一个对象关联方法被隐式传递给调用它的对象方法能够对类的实例中包含的数据进行操作。
因此C 和 Go 只有函数Java 和 C# 只有方法,在 C++, Python 中取决于它是否属于一个类。
!!! question "图片“空间复杂度的常见类型”反映的是否是占用空间的绝对大小?"
不是,该图片展示的是空间复杂度(即增长趋势),而不是占用空间的绝对大小。每个曲线都包含一个常数项,用来把所有曲线的取值范围压缩到一个视觉舒适的范围内。

@ -538,7 +538,7 @@ index = hash(key) % capacity
## 哈希冲突与扩容
本质上看,哈希函数的作用是输入空间(`key` 范围)映射到输出空间(数组索引范围),而输入空间往往远大于输出空间。因此,**理论上一定存在“多个输入对应相同输出”的情况**。
本质上看,哈希函数的作用是输入空间(`key` 范围)映射到输出空间(数组索引范围),而输入空间往往远大于输出空间。因此,**理论上一定存在“多个输入对应相同输出”的情况**。
对于上述示例中的哈希函数,当输入的 `key` 后两位相同时,哈希函数的输出结果也相同。例如,查询学号为 12836 和 20336 的两个学生时,我们得到:

@ -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 "为什么在线性探测中,查找元素的时候会出现哈希冲突呢?"

@ -6,9 +6,10 @@
- 完全二叉树非常适合用数组表示,因此我们通常使用数组来存储堆。
- 堆化操作用于维护堆的性质,在入堆和出堆操作中都会用到。
- 输入 $n$ 个元素并建堆的时间复杂度可以优化至 $O(n)$ ,非常高效。
- Top-K 是一个经典算法问题,可以使用堆数据结构高效解决,时间复杂度为 $O(n \log k)$ 。
## Q & A
!!! question "数据结构的“堆”与内存管理的“堆”是同一个概念吗?"
两者不是同一个概念,只是碰巧都叫堆。计算机系统内存中的堆是动态内存分配的一部分,程序在运行时可以使用它来存储数据。程序可以请求一定量的堆内存,用于存储如对象和数组等复杂结构。当这些数据不再需要时,程序需要释放这些内存,以防止内存泄露。相较于栈内存,堆内存的管理和使用需要更谨慎,不恰当的使用可能会导致内存泄露和野指针等问题。

@ -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)

@ -19,7 +19,7 @@
是的,因为高度和深度通常定义为“走过边的数量”。
!!! question "二叉树中的插入与删除一般都是由一套操作配合完成的,这里的“一套操作”指什么呢?可以理解为资源的子节点的资源释放吗?"
拿二叉搜索树来举例,删除节点操作要分为三种情况处理,其中每种情况都需要进行多个步骤的节点操作。
!!! question "为什么 DFS 遍历二叉树有前、中、后三种顺序,分别有什么用呢?"

Loading…
Cancel
Save