diff --git a/chapter_computational_complexity/index.md b/chapter_computational_complexity/index.md index a3d3bcc1c..231efa2ca 100644 --- a/chapter_computational_complexity/index.md +++ b/chapter_computational_complexity/index.md @@ -3,11 +3,11 @@ comments: true icon: material/timer-sand --- -# 第 2 章 时空复杂度 +# 第 2 章 复杂度分析
图 2-16 常见的空间复杂度类型
-!!! tip - - 部分示例代码需要一些前置知识,包括数组、链表、二叉树、递归算法等。如果你遇到看不懂的地方,可以在学完后面章节后再来复习。 - ### 1. 常数阶 $O(1)$ 常数阶常见于数量与输入数据大小 $n$ 无关的常量、变量、对象。 diff --git a/chapter_computational_complexity/summary.md b/chapter_computational_complexity/summary.md index 82c88cc4c..f4b9df6b2 100644 --- a/chapter_computational_complexity/summary.md +++ b/chapter_computational_complexity/summary.md @@ -15,7 +15,7 @@ comments: true - 时间复杂度用于衡量算法运行时间随数据量增长的趋势,可以有效评估算法效率,但在某些情况下可能失效,如在输入的数据量较小或时间复杂度相同时,无法精确对比算法效率的优劣。 - 最差时间复杂度使用大 $O$ 符号表示,对应函数渐近上界,反映当 $n$ 趋向正无穷时,操作数量 $T(n)$ 的增长级别。 - 推算时间复杂度分为两步,首先统计操作数量,然后判断渐近上界。 -- 常见时间复杂度从小到大排列有 $O(1)$、$O(\log n)$、$O(n)$、$O(n \log n)$、$O(n^2)$、$O(2^n)$、$O(n!)$ 等。 +- 常见时间复杂度从小到大排列有 $O(1)$、$O(\log n)$、$O(n)$、$O(n \log n)$、$O(n^2)$、$O(2^n)$ 和 $O(n!)$ 等。 - 某些算法的时间复杂度非固定,而是与输入数据的分布有关。时间复杂度分为最差、最佳、平均时间复杂度,最佳时间复杂度几乎不用,因为输入数据一般需要满足严格条件才能达到最佳情况。 - 平均时间复杂度反映算法在随机数据输入下的运行效率,最接近实际应用中的算法性能。计算平均时间复杂度需要统计输入数据分布以及综合后的数学期望。 @@ -24,7 +24,7 @@ comments: true - 空间复杂度的作用类似于时间复杂度,用于衡量算法占用空间随数据量增长的趋势。 - 算法运行过程中的相关内存空间可分为输入空间、暂存空间、输出空间。通常情况下,输入空间不计入空间复杂度计算。暂存空间可分为指令空间、数据空间、栈帧空间,其中栈帧空间通常仅在递归函数中影响空间复杂度。 - 我们通常只关注最差空间复杂度,即统计算法在最差输入数据和最差运行时间点下的空间复杂度。 -- 常见空间复杂度从小到大排列有 $O(1)$、$O(\log n)$、$O(n)$、$O(n^2)$、$O(2^n)$ 等。 +- 常见空间复杂度从小到大排列有 $O(1)$、$O(\log n)$、$O(n)$、$O(n^2)$ 和 $O(2^n)$ 等。 ## 2.5.1 Q & A @@ -42,7 +42,7 @@ comments: true - Java 和 C# 是面向对象的编程语言,代码块(方法)通常都是作为某个类的一部分。静态方法的行为类似于函数,因为它被绑定在类上,不能访问特定的实例变量。 - C++ 和 Python 既支持过程式编程(函数),也支持面向对象编程(方法)。 -!!! question "图“空间复杂度的常见类型”反映的是否是占用空间的绝对大小?" +!!! question "图“常见的空间复杂度类型”反映的是否是占用空间的绝对大小?" 不是,该图片展示的是空间复杂度,其反映的是增长趋势,而不是占用空间的绝对大小。 diff --git a/chapter_computational_complexity/time_complexity.md b/chapter_computational_complexity/time_complexity.md index 7d8f253a8..f063701bd 100755 --- a/chapter_computational_complexity/time_complexity.md +++ b/chapter_computational_complexity/time_complexity.md @@ -619,18 +619,11 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因 我们将线性阶的时间复杂度记为 $O(n)$ ,这个数学符号称为「大 $O$ 记号 big-$O$ notation」,表示函数 $T(n)$ 的「渐近上界 asymptotic upper bound」。 -时间复杂度分析本质上是计算“操作数量函数 $T(n)$”的渐近上界。接下来,我们来看函数渐近上界的数学定义。 +时间复杂度分析本质上是计算“操作数量函数 $T(n)$”的渐近上界,其具有明确的数学定义。 !!! abstract "函数渐近上界" - 若存在正实数 $c$ 和实数 $n_0$ ,使得对于所有的 $n > n_0$ ,均有 - $$ - T(n) \leq c \cdot f(n) - $$ - 则可认为 $f(n)$ 给出了 $T(n)$ 的一个渐近上界,记为 - $$ - T(n) = O(f(n)) - $$ + 若存在正实数 $c$ 和实数 $n_0$ ,使得对于所有的 $n > n_0$ ,均有 $T(n) \leq c \cdot f(n)$ ,则可认为 $f(n)$ 给出了 $T(n)$ 的一个渐近上界,记为 $T(n) = O(f(n))$ 。 如图 2-8 所示,计算渐近上界就是寻找一个函数 $f(n)$ ,使得当 $n$ 趋向于无穷大时,$T(n)$ 和 $f(n)$ 处于相同的增长级别,仅相差一个常数项 $c$ 的倍数。 @@ -650,17 +643,9 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因 1. **忽略 $T(n)$ 中的常数项**。因为它们都与 $n$ 无关,所以对时间复杂度不产生影响。 2. **省略所有系数**。例如,循环 $2n$ 次、$5n + 1$ 次等,都可以简化记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度没有影响。 -3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。 - -以下代码与公式分别展示了使用上述技巧前后的统计结果。两者推出的时间复杂度相同,都为 $O(n^2)$ 。 +3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用第 `1.` 点和第 `2.` 点的技巧。、 -$$ -\begin{aligned} -T(n) & = 2n(n + 1) + (5n + 1) + 2 & \text{完整统计 (-.-|||)} \newline -& = 2n^2 + 7n + 3 \newline -T(n) & = n^2 + n & \text{偷懒统计 (o.O)} -\end{aligned} -$$ +给定一个函数,我们可以用上述技巧来统计操作数量。 === "Java" @@ -875,6 +860,16 @@ $$ } ``` +以下公式展示了使用上述技巧前后的统计结果,两者推出的时间复杂度都为 $O(n^2)$ 。 + +$$ +\begin{aligned} +T(n) & = 2n(n + 1) + (5n + 1) + 2 & \text{完整统计 (-.-|||)} \newline +& = 2n^2 + 7n + 3 \newline +T(n) & = n^2 + n & \text{偷懒统计 (o.O)} +\end{aligned} +$$ + ### 2. 第二步:判断渐近上界 **时间复杂度由多项式 $T(n)$ 中最高阶的项来决定**。这是因为在 $n$ 趋于无穷大时,最高阶的项将发挥主导作用,其他项的影响都可以被忽略。 @@ -910,15 +905,11 @@ $$图 2-9 常见的时间复杂度类型
-!!! tip - - 部分示例代码需要一些预备知识,包括数组、递归等。如果你遇到不理解的部分,可以在学完后面章节后再回顾。现阶段,请先专注于理解时间复杂度的含义和推算方法。 - ### 1. 常数阶 $O(1)$ 常数阶的操作数量与输入数据大小 $n$ 无关,即不随着 $n$ 的变化而变化。 -对于以下算法,尽管操作数量 `size` 可能很大,但由于其与输入数据大小 $n$ 无关,因此时间复杂度仍为 $O(1)$ : +在以下函数中,尽管操作数量 `size` 可能很大,但由于其与输入数据大小 $n$ 无关,因此时间复杂度仍为 $O(1)$ : === "Java" @@ -1407,7 +1398,7 @@ $$ ### 3. 平方阶 $O(n^2)$ -平方阶的操作数量相对于输入数据大小以平方级别增长。平方阶通常出现在嵌套循环中,外层循环和内层循环都为 $O(n)$ ,因此总体为 $O(n^2)$ : +平方阶的操作数量相对于输入数据大小 $n$ 以平方级别增长。平方阶通常出现在嵌套循环中,外层循环和内层循环都为 $O(n)$ ,因此总体为 $O(n^2)$ : === "Java" @@ -2967,7 +2958,7 @@ $$ ## 2.3.5 最差、最佳、平均时间复杂度 -**算法的时间效率往往不是固定的,而是与输入数据的分布有关**。假设输入一个长度为 $n$ 的数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,每个数字只出现一次,但元素顺序是随机打乱的,任务目标是返回元素 $1$ 的索引。我们可以得出以下结论。 +**算法的时间效率往往不是固定的,而是与输入数据的分布有关**。假设输入一个长度为 $n$ 的数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,每个数字只出现一次;但元素顺序是随机打乱的,任务目标是返回元素 $1$ 的索引。我们可以得出以下结论。 - 当 `nums = [?, ?, ..., 1]` ,即当末尾元素是 $1$ 时,需要完整遍历数组,**达到最差时间复杂度 $O(n)$** 。 - 当 `nums = [1, ?, ?, ...]` ,即当首个元素为 $1$ 时,无论数组多长都不需要继续遍历,**达到最佳时间复杂度 $\Omega(1)$** 。 diff --git a/chapter_introduction/algorithms_are_everywhere.md b/chapter_introduction/algorithms_are_everywhere.md index f164e7229..235b2d976 100644 --- a/chapter_introduction/algorithms_are_everywhere.md +++ b/chapter_introduction/algorithms_are_everywhere.md @@ -45,7 +45,7 @@ comments: true 上述整理扑克牌的方法本质上是“插入排序”算法,它在处理小型数据集时非常高效。许多编程语言的排序库函数中都存在插入排序的身影。 -**例三:货币找零**。假设我们在超市购买了 $69$ 元的商品,给收银员付了 $100$ 元,则收银员需要找我们 $31$ 元。他会很自然地完成图 1-3 所示的思考。 +**例三:货币找零**。假设我们在超市购买了 $69$ 元的商品,给了收银员 $100$ 元,则收银员需要找我们 $31$ 元。他会很自然地完成如图 1-3 所示的思考。 1. 可选项是比 $31$ 元面值更小的货币,包括 $1$ 元、$5$ 元、$10$ 元、$20$ 元。 2. 从可选项中拿出最大的 $20$ 元,剩余 $31 - 20 = 11$ 元。 @@ -63,4 +63,4 @@ comments: true !!! tip - 如果你对数据结构、算法、数组和二分查找等概念仍感到一知半解,请不要担心,继续往下阅读,这本书将引导你迈入数据结构与算法的知识殿堂。 + 如果你对数据结构、算法、数组和二分查找等概念仍感到一知半解,请继续往下阅读,这本书将引导你迈入数据结构与算法的知识殿堂。 diff --git a/chapter_introduction/what_is_dsa.md b/chapter_introduction/what_is_dsa.md index 8fd0862f4..903ca9ffb 100644 --- a/chapter_introduction/what_is_dsa.md +++ b/chapter_introduction/what_is_dsa.md @@ -27,7 +27,7 @@ comments: true ## 1.2.3 数据结构与算法的关系 -数据结构与算法高度相关、紧密结合,具体表现在图 1-4 所示的几个方面。 +如图 1-4 所示,数据结构与算法高度相关、紧密结合,具体表现以下三个方面。 - 数据结构是算法的基石。数据结构为算法提供了结构化存储的数据,以及用于操作数据的方法。 - 算法是数据结构发挥作用的舞台。数据结构本身仅存储数据信息,结合算法才能解决特定问题。