From 1028c0c0830d1fa28c4031156fbfc31a805098f3 Mon Sep 17 00:00:00 2001 From: Sizhuo Long <114939201+longsizhuo123@users.noreply.github.com> Date: Fri, 26 May 2023 21:27:34 +0800 Subject: [PATCH] add Q&A about array_and_linklist and fix miss one "$" in c_c chapter (#515) * fix "$" in c_c summary and add QA of array_linklist in summary * Update summary.md * Update summary.md --------- Co-authored-by: Yudong Jin --- docs/chapter_array_and_linkedlist/summary.md | 44 +++++++++++++++++++ .../summary.md | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/chapter_array_and_linkedlist/summary.md b/docs/chapter_array_and_linkedlist/summary.md index 6833db8e1..6e955a04c 100644 --- a/docs/chapter_array_and_linkedlist/summary.md +++ b/docs/chapter_array_and_linkedlist/summary.md @@ -32,3 +32,47 @@ | 删除元素 | $O(N)$ | $O(1)$ | + +## Q & A + +!!! question "数组存储在栈上和存储在堆上,对时间效率和空间效率是否有影响?" + + 栈内存分配由编译器自动完成,而堆内存由程序员在代码中分配(注意,这里的栈和堆和数据结构中的栈和堆不是同一概念)。 + + 1. 栈不灵活,分配的内存大小不可更改;堆相对灵活,可以动态分配内存; + 2. 栈是一块比较小的内存,容易出现内存不足;堆内存很大,但是由于是动态分配,容易碎片化,管理堆内存的难度更大、成本更高; + 3. 访问栈比访问堆更快,因为栈内存较小、对缓存友好,堆帧分散在很大的空间内,会出现更多的缓存未命中; + +!!! question "为什么数组会强调要求相同类型的元素,而在链表中却没有强调同类型呢?" + + 链表由结点组成,结点之间由指针连接,各个结点可以存储不同类型的数据,例如 int, double, string, object 等。 + + 相对地,数组元素则必须是相同类型的,这样才能通过计算偏移量来获取对应元素位置。例如,如果数组同时包含 int 和 long 两种类型,单个元素分别占用 4 bytes 和 8 bytes ,那么此时就不能用以下公式计算偏移量了,因为数组中包含了两种 `elementLength` 。 + + ``` + // 元素内存地址 = 数组内存地址 + 元素长度 * 元素索引 + elementAddr = firtstElementAddr + elementLength * elementIndex + ``` + +!!! question "删除节点后,是否需要把 `P.next` 设为 $\text{None}$ 呢?" + + 不修改 `P.next` 也可以。从该链表的角度看,从头结点遍历到尾结点已经遇不到 `P` 了。这意味着结点 `P` 已经从链表中删除了,此时结点 `P` 指向哪里都不会对这条链表产生影响了。 + +!!! question "在链表中插入和删除操作的时间复杂度是 $O(1)$ 。但是增删之前都需要 $O(n)$ 查找元素,那为什么时间复杂度不是 $O(n)$ 呢?" + + 如果是先查找元素、再删除元素,确实是 $O(n)$ 。然而,链表的 $O(1)$ 增删的优势可以在其他应用上得到体现。例如,双向队列适合使用链表实现,我们维护一个指针变量始终指向头结点、尾结点,每次插入与删除操作都是 $O(1)$ 。 + +!!! question "图片“链表定义与存储方式”中,浅蓝色的存储结点指针是占用一块内存地址吗?还是和结点值各占一半呢?" + + 文中只是一个示意图,只是定性表示。定量的话需要根据具体情况分析: + + - 不同类型的结点值占用的空间是不同的,比如 int, long, double, 或者是类的实例等等。 + - 指针变量占用的内存空间大小根据所使用的操作系统及编译环境而定,大多为 8 字节或 4 字节。 + +!!! question "在列表末尾添加元素是否时时刻刻都为 $O(1)$ ?" + + 如果添加元素时超出列表长度,则需要先扩容列表再添加。系统会申请一块新的内存,并将原列表的所有元素搬运过去,这时候时间复杂度就会是 $O(n)$ 。 + +!!! question "“列表的出现大大提升了数组的实用性,但副作用是会造成部分内存空间浪费”,这里的空间浪费是指额外增加的变量如容量、长度、扩容倍数所占的内存吗?" + + 这里的空间浪费主要有两方面含义:一方面,列表都会设定一个初始长度,我们不一定需要用这么多。另一方面,为了防止频繁扩容,扩容一般都会乘以一个系数,比如 $\times 1.5$ 。这样一来,也会出现很多空位,我们通常不能完全填满它们。 diff --git a/docs/chapter_computational_complexity/summary.md b/docs/chapter_computational_complexity/summary.md index 0ef72be9a..c5dce824b 100644 --- a/docs/chapter_computational_complexity/summary.md +++ b/docs/chapter_computational_complexity/summary.md @@ -37,4 +37,4 @@ !!! question "图片“空间复杂度的常见类型”反映的是否是占用空间的绝对大小?" 不是,该图片展示的是空间复杂度(即增长趋势),而不是占用空间的绝对大小。每个曲线都包含一个常数项,用来把所有曲线的取值范围压缩到一个视觉舒适的范围内。 - 实际中,因为我们通常不知道每个方法的“常数项”复杂度是多少,所以一般无法仅凭复杂度来选择 $n = 8 之下的最优解法;但相对地 $n = 8^5$ 就很好选了,这是复杂度占主导的情况。 + 实际中,因为我们通常不知道每个方法的“常数项”复杂度是多少,所以一般无法仅凭复杂度来选择 $n = 8$ 之下的最优解法;但相对地 $n = 8^5$ 就很好选了,这是复杂度占主导的情况。