diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index de25369a4..050a6b395 100644 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -2,7 +2,7 @@ comments: true --- -# 数组 +# 4.1. 数组 「数组 Array」是一种将 **相同类型元素** 存储在 **连续内存空间** 的数据结构,将元素在数组中的位置称为元素的「索引 Index」。 @@ -89,7 +89,7 @@ comments: true let nums = [1, 3, 2, 5, 4] ``` -## 数组优点 +## 4.1.1. 数组优点 **在数组中访问元素非常高效**。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。 @@ -217,7 +217,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -## 数组缺点 +## 4.1.2. 数组缺点 **数组在初始化后长度不可变**。由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。 @@ -551,7 +551,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -## 数组常用操作 +## 4.1.3. 数组常用操作 **数组遍历**。以下介绍两种常用的遍历方法。 @@ -809,7 +809,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -## 数组典型应用 +## 4.1.4. 数组典型应用 **随机访问**。如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。 diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index 8dd069788..15c94284e 100644 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -2,7 +2,7 @@ comments: true --- -# 链表 +# 4.2. 链表 !!! note "引言" @@ -277,7 +277,7 @@ comments: true n3.next = n4 ``` -## 链表优点 +## 4.2.1. 链表优点 **在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 @@ -465,7 +465,7 @@ comments: true } ``` -## 链表缺点 +## 4.2.2. 链表缺点 **链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 @@ -593,7 +593,7 @@ comments: true **链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 -## 链表常用操作 +## 4.2.3. 链表常用操作 **遍历链表查找**。遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。 @@ -736,7 +736,7 @@ comments: true } ``` -## 常见链表类型 +## 4.2.4. 常见链表类型 **单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。 diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index e548a76f9..76ea55dfb 100644 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -2,13 +2,13 @@ comments: true --- -# 列表 +# 4.3. 列表 **由于长度不可变,数组的实用性大大降低**。在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。 为了解决此问题,诞生了一种被称为「列表 List」的数据结构。列表可以被理解为长度可变的数组,因此也常被称为「动态数组 Dynamic Array」。列表基于数组实现,继承了数组的优点,同时还可以在程序运行中实时扩容。在列表中,我们可以自由地添加元素,而不用担心超过容量限制。 -## 列表常用操作 +## 4.3.1. 列表常用操作 **初始化列表**。我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。 @@ -630,7 +630,7 @@ comments: true list.sort() // 排序后,列表元素从小到大排列 ``` -## 列表简易实现 * +## 4.3.2. 列表简易实现 * 为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点: diff --git a/docs/chapter_array_and_linkedlist/summary.md b/docs/chapter_array_and_linkedlist/summary.md index 60fe604f0..3b6ac2cb0 100644 --- a/docs/chapter_array_and_linkedlist/summary.md +++ b/docs/chapter_array_and_linkedlist/summary.md @@ -2,14 +2,14 @@ comments: true --- -# 小结 +# 4.4. 小结 - 数组和链表是两种基本数据结构,代表了数据在计算机内存中的两种存储方式,即连续空间存储和离散空间存储。两者的优点与缺点呈现出此消彼长的关系。 - 数组支持随机访问、内存空间占用小;但插入与删除元素效率低,且初始化后长度不可变。 - 链表可通过更改指针实现高效的结点插入与删除,并且可以灵活地修改长度;但结点访问效率低、占用内存多。常见的链表类型有单向链表、循环链表、双向链表。 - 列表又称动态数组,是基于数组实现的一种数据结构,其保存了数组的优势,且可以灵活改变长度。列表的出现大大提升了数组的实用性,但副作用是会造成部分内存空间浪费。 -## 数组 VS 链表 +## 4.4.1. 数组 VS 链表
Table. 数组与链表特点对比
diff --git a/docs/chapter_computational_complexity/performance_evaluation.md b/docs/chapter_computational_complexity/performance_evaluation.md index 9d6fd6628..6c236c864 100644 --- a/docs/chapter_computational_complexity/performance_evaluation.md +++ b/docs/chapter_computational_complexity/performance_evaluation.md @@ -2,9 +2,9 @@ comments: true --- -# 算法效率评估 +# 2.1. 算法效率评估 -## 算法评价维度 +## 2.1.1. 算法评价维度 在开始学习算法之前,我们首先要想清楚算法的设计目标是什么,或者说,如何来评判算法的好与坏。整体上看,我们设计算法时追求两个层面的目标。 @@ -18,7 +18,7 @@ comments: true 数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。 -## 效率评估方法 +## 2.1.2. 效率评估方法 ### 实际测试 @@ -36,7 +36,7 @@ comments: true **复杂度分析克服了实际测试方法的弊端**。一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。 -## 复杂度分析的重要性 +## 2.1.3. 复杂度分析重要性 复杂度分析给出一把评价算法效率的“标尺”,告诉我们执行某个算法需要多少时间和空间资源,也让我们可以开展不同算法之间的效率对比。 diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index 96873fc12..479839647 100644 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -2,11 +2,11 @@ comments: true --- -# 空间复杂度 +# 2.3. 空间复杂度 「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势**。这个概念与时间复杂度很类似。 -## 算法相关空间 +## 2.3.1. 算法相关空间 算法运行中,使用的内存空间主要有以下几种: @@ -202,7 +202,7 @@ comments: true } ``` -## 推算方法 +## 2.3.2. 推算方法 空间复杂度的推算方法和时间复杂度总体类似,只是从统计“计算操作数量”变为统计“使用空间大小”。与时间复杂度不同的是,**我们一般只关注「最差空间复杂度」**。这是因为内存空间是一个硬性要求,我们必须保证在所有输入数据下都有足够的内存空间预留。 @@ -452,7 +452,7 @@ comments: true } ``` -## 常见类型 +## 2.3.3. 常见类型 设输入数据大小为 $n$ ,常见的空间复杂度类型有(从低到高排列) diff --git a/docs/chapter_computational_complexity/space_time_tradeoff.md b/docs/chapter_computational_complexity/space_time_tradeoff.md index a265aba99..af865f2ad 100644 --- a/docs/chapter_computational_complexity/space_time_tradeoff.md +++ b/docs/chapter_computational_complexity/space_time_tradeoff.md @@ -2,7 +2,7 @@ comments: true --- -# 权衡时间与空间 +# 2.4. 权衡时间与空间 理想情况下,我们希望算法的时间复杂度和空间复杂度都能够达到最优,而实际上,同时优化时间复杂度和空间复杂度是非常困难的。 @@ -10,7 +10,7 @@ comments: true 大多数情况下,时间都是比空间更宝贵的,只要空间复杂度不要太离谱、能接受就行,**因此以空间换时间最为常用**。 -## 示例题目 * +## 2.4.1. 示例题目 * 以 LeetCode 全站第一题 [两数之和](https://leetcode.cn/problems/two-sum/) 为例。 diff --git a/docs/chapter_computational_complexity/summary.md b/docs/chapter_computational_complexity/summary.md index 0a1ee80be..acd745f9f 100644 --- a/docs/chapter_computational_complexity/summary.md +++ b/docs/chapter_computational_complexity/summary.md @@ -2,7 +2,7 @@ comments: true --- -# 小结 +# 2.5. 小结 ### 算法效率评估 diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index def052c92..97c9120c8 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -2,9 +2,9 @@ comments: true --- -# 时间复杂度 +# 2.2. 时间复杂度 -## 统计算法运行时间 +## 2.2.1. 统计算法运行时间 运行时间能够直观且准确地体现出算法的效率水平。如果我们想要 **准确预估一段代码的运行时间** ,该如何做呢? @@ -155,7 +155,7 @@ $$ 但实际上, **统计算法的运行时间既不合理也不现实**。首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。 -## 统计时间增长趋势 +## 2.2.2. 统计时间增长趋势 「时间复杂度分析」采取了不同的做法,其统计的不是算法运行时间,而是 **算法运行时间随着数据量变大时的增长趋势** 。 @@ -369,7 +369,7 @@ $$ **时间复杂度也存在一定的局限性**。比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。 -## 函数渐近上界 +## 2.2.3. 函数渐近上界 设算法「计算操作数量」为 $T(n)$ ,其是一个关于输入数据大小 $n$ 的函数。例如,以下算法的操作数量为 @@ -530,11 +530,11 @@ $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得 渐近上界的数学味儿有点重,如果你感觉没有完全理解,无需担心,因为在实际使用中我们只需要会推算即可,数学意义可以慢慢领悟。 -## 推算方法 +## 2.2.4. 推算方法 推算出 $f(n)$ 后,我们就得到时间复杂度 $O(f(n))$ 。那么,如何来确定渐近上界 $f(n)$ 呢?总体分为两步,首先「统计操作数量」,然后「判断渐近上界」。 -### 1. 统计操作数量 +### 1) 统计操作数量 对着代码,从上到下一行一行地计数即可。然而,**由于上述 $c \cdot f(n)$ 中的常数项 $c$ 可以取任意大小,因此操作数量 $T(n)$ 中的各种系数、常数项都可以被忽略**。根据此原则,可以总结出以下计数偷懒技巧: @@ -725,7 +725,7 @@ $$ } ``` -### 2. 判断渐近上界 +### 2) 判断渐近上界 **时间复杂度由多项式 $T(n)$ 中最高阶的项来决定**。这是因为在 $n$ 趋于无穷大时,最高阶的项将处于主导作用,其它项的影响都可以被忽略。 @@ -743,7 +743,7 @@ $$ -## 常见类型 +## 2.2.5. 常见类型 设输入数据大小为 $n$ ,常见的时间复杂度类型有(从低到高排列) @@ -2309,7 +2309,7 @@ $$Fig. 阶乘阶的时间复杂度
-## 最差、最佳、平均时间复杂度 +## 2.2.6. 最差、最佳、平均时间复杂度 **某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关**。举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论: diff --git a/docs/chapter_data_structure/classification_of_data_structure.md b/docs/chapter_data_structure/classification_of_data_structure.md index a2f5221b8..1b8d7b739 100644 --- a/docs/chapter_data_structure/classification_of_data_structure.md +++ b/docs/chapter_data_structure/classification_of_data_structure.md @@ -2,11 +2,11 @@ comments: true --- -# 数据结构分类 +# 3.2. 数据结构分类 数据结构主要可根据「逻辑结构」和「物理结构」两种角度进行分类。 -## 逻辑结构:线性与非线性 +## 3.2.1. 逻辑结构:线性与非线性 **「逻辑结构」反映了数据之间的逻辑关系**。数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。 @@ -19,7 +19,7 @@ comments: trueFig. 线性与非线性数据结构
-## 物理结构:连续与离散 +## 3.2.2. 物理结构:连续与离散 !!! note diff --git a/docs/chapter_data_structure/data_and_memory.md b/docs/chapter_data_structure/data_and_memory.md index 413841059..531744fb3 100644 --- a/docs/chapter_data_structure/data_and_memory.md +++ b/docs/chapter_data_structure/data_and_memory.md @@ -2,9 +2,9 @@ comments: true --- -# 数据与内存 +# 3.1. 数据与内存 -## 基本数据类型 +## 3.1.1. 基本数据类型 谈到计算机中的数据,我们能够想到文本、图片、视频、语音、3D 模型等等,这些数据虽然组织形式不同,但是有一个共同点,即都是由各种基本数据类型构成的。 @@ -128,7 +128,7 @@ comments: true let booleans = Array(repeating: Bool(), count: 5) ``` -## 计算机内存 +## 3.1.2. 计算机内存 在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度较快,但容量较小(通常为 GB 级别)。 diff --git a/docs/chapter_data_structure/summary.md b/docs/chapter_data_structure/summary.md index e51e4dc5b..5340746a3 100644 --- a/docs/chapter_data_structure/summary.md +++ b/docs/chapter_data_structure/summary.md @@ -2,7 +2,7 @@ comments: true --- -# 小结 +# 3.3. 小结 - 整数 byte, short, int, long 、浮点数 float, double 、字符 char 、布尔 boolean 是计算机中的基本数据类型,占用空间的大小决定了它们的取值范围。 - 在程序运行时,数据存储在计算机的内存中。内存中每块空间都有独立的内存地址,程序是通过内存地址来访问数据的。 diff --git a/docs/chapter_graph/basic_operation_of_graph.md b/docs/chapter_graph/basic_operation_of_graph.md index 3710c8b5a..920663b54 100644 --- a/docs/chapter_graph/basic_operation_of_graph.md +++ b/docs/chapter_graph/basic_operation_of_graph.md @@ -2,11 +2,11 @@ comments: true --- -# 图基础操作 +# 9.2. 图基础操作 图的基础操作分为对「边」的操作和对「顶点」的操作,在「邻接矩阵」和「邻接表」这两种表示下的实现方式不同。 -## 基于邻接矩阵的实现 +## 9.2.1. 基于邻接矩阵的实现 设图的顶点总数为 $n$ ,则有: @@ -162,7 +162,7 @@ comments: true ``` -## 基于邻接表的实现 +## 9.2.2. 基于邻接表的实现 设图的顶点总数为 $n$ 、边总数为 $m$ ,则有: @@ -309,7 +309,7 @@ comments: true ``` -## 效率对比 +## 9.2.3. 效率对比 设图中共有 $n$ 个顶点和 $m$ 条边,下表为邻接矩阵和邻接表的时间和空间效率对比。 diff --git a/docs/chapter_graph/graph.md b/docs/chapter_graph/graph.md index ca81b20ae..5f8d861e5 100644 --- a/docs/chapter_graph/graph.md +++ b/docs/chapter_graph/graph.md @@ -2,7 +2,7 @@ comments: true --- -# 图 +# 9.1. 图 「图 Graph」是一种非线性数据结构,由「顶点 Vertex」和「边 Edge」组成。我们可将图 $G$ 抽象地表示为一组顶点 $V$ 和一组边 $E$ 的集合。例如,以下表示一个包含 5 个顶点和 7 条边的图 @@ -18,7 +18,7 @@ $$ 那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看成一种从「链表」拓展而来的数据结构。**相比线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,也从而更为复杂**。 -## 图常见类型 +## 9.1.1. 图常见类型 根据边是否有方向,分为「无向图 Undirected Graph」和「有向图 Directed Graph」。 @@ -38,13 +38,13 @@ $$ ![weighted_graph](graph.assets/weighted_graph.png) -## 图常用术语 +## 9.1.2. 图常用术语 - 「邻接 Adjacency」:当两顶点之间有边相连时,称此两顶点“邻接”。 - 「路径 Path」:从顶点 A 到顶点 B 走过的边构成的序列,被称为从 A 到 B 的“路径”。 - 「度 Degree」表示一个顶点具有多少条边。对于有向图,「入度 In-Degree」表示有多少条边指向该顶点,「出度 Out-Degree」表示有多少条边从该顶点指出。 -## 图的表示 +## 9.1.3. 图的表示 图的常用表示方法有「邻接矩阵」和「邻接表」。以下使用「无向图」来举例。 @@ -72,7 +72,7 @@ $$ 观察上图发现,**邻接表结构与哈希表「链地址法」非常相似,因此我们也可以用类似方法来优化效率**。比如,当链表较长时,可以把链表转化为「AVL 树」,从而将时间效率从 $O(n)$ 优化至 $O(\log n)$ ,还可以通过中序遍历获取有序序列;还可以将链表转化为 HashSet(即哈希表),将时间复杂度降低至 $O(1)$ ,。 -## 图常见应用 +## 9.1.4. 图常见应用 现实中的许多系统都可以使用图来建模,对应的待求解问题也可以被约化为图计算问题。 diff --git a/docs/chapter_hashing/hash_collision.md b/docs/chapter_hashing/hash_collision.md index d17bb5911..269254c6c 100644 --- a/docs/chapter_hashing/hash_collision.md +++ b/docs/chapter_hashing/hash_collision.md @@ -2,7 +2,7 @@ comments: true --- -# 哈希冲突 +# 6.2. 哈希冲突 理想情况下,哈希函数应该为每个输入产生唯一的输出,使得 key 和 value 一一对应。而实际上,往往存在向哈希函数输入不同的 key 而产生相同输出的情况,这种情况被称为「哈希冲突 Hash Collision」。哈希冲突会导致查询结果错误,从而严重影响哈希表的可用性。 @@ -12,7 +12,7 @@ comments: true 另一方面,**考虑通过优化数据结构以缓解哈希冲突**,常见的方法有「链式地址」和「开放寻址」。 -## 哈希表扩容 +## 6.2.1. 哈希表扩容 「负载因子 Load Factor」定义为 **哈希表中元素数量除以桶槽数量(即数组大小)**,代表哈希冲突的严重程度。 @@ -20,7 +20,7 @@ comments: true 与数组扩容类似,**哈希表扩容操作的开销很大**,因为需要将所有键值对从原哈希表依次移动至新哈希表。 -## 链式地址 +## 6.2.2. 链式地址 在原始哈希表中,桶内的每个地址只能存储一个元素(即键值对)。**考虑将单个元素转化成一个链表,将所有冲突元素都存储在一个链表中**。 @@ -39,7 +39,7 @@ comments: true 为了缓解时间效率问题,**可以把「链表」转化为「AVL 树」或「红黑树」**,将查询操作的时间复杂度优化至 $O(\log n)$ 。 -## 开放寻址 +## 6.2.3. 开放寻址 「开放寻址」不引入额外数据结构,而是通过“多次探测”来解决哈希冲突。根据探测方法的不同,主要分为 **线性探测、平方探测、多次哈希**。 diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index be54c7df1..313b085a7 100644 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -2,7 +2,7 @@ comments: true --- -# 哈希表 +# 6.1. 哈希表 哈希表通过建立「键 key」和「值 value」之间的映射,实现高效的元素查找。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 $O(1)$ 。 @@ -12,7 +12,7 @@ comments: trueFig. 哈希表抽象表示
-## 哈希表效率 +## 6.1.1. 哈希表效率 除了哈希表之外,还可以使用以下数据结构来实现上述查询功能: @@ -33,7 +33,7 @@ comments: true -## 哈希表常用操作 +## 6.1.2. 哈希表常用操作 哈希表的基本操作包括 **初始化、查询操作、添加与删除键值对**。 @@ -380,7 +380,7 @@ comments: true } ``` -## 哈希函数 +## 6.1.3. 哈希函数 哈希表中存储元素的数据结构被称为「桶 Bucket」,底层实现可能是数组、链表、二叉树(红黑树),或是它们的组合。 @@ -851,7 +851,7 @@ $$ } ``` -## 哈希冲突 +## 6.1.4. 哈希冲突 细心的同学可能会发现,**哈希函数 $f(x) = x \% 100$ 会在某些情况下失效**。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 $12836$ 和 $20336$ ,则有 diff --git a/docs/chapter_hashing/summary.md b/docs/chapter_hashing/summary.md index 5f58b7594..a66b041a3 100644 --- a/docs/chapter_hashing/summary.md +++ b/docs/chapter_hashing/summary.md @@ -2,4 +2,4 @@ comments: true --- -# 小结 +# 6.3. 小结 diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md index 0458448a7..d674b3ba0 100644 --- a/docs/chapter_heap/heap.md +++ b/docs/chapter_heap/heap.md @@ -2,22 +2,24 @@ comments: true --- -# 堆 +# 8.1. # 1.1 堆 「堆 Heap」是一颗限定条件下的「完全二叉树」。根据成立条件,堆主要分为两种类型: - 「大顶堆 Max Heap」,任意结点的值 $\geq$ 其子结点的值; - 「小顶堆 Min Heap」,任意结点的值 $\leq$ 其子结点的值; -![min_heap_and_max_heap](heap.assets/min_heap_and_max_heap.png) + -## 堆术语与性质 +## 8.1.1. 堆术语与性质 - 由于堆是完全二叉树,因此最底层结点靠左填充,其它层结点皆被填满。 - 二叉树中的根结点对应「堆顶」,底层最靠右结点对应「堆底」。 - 对于大顶堆 / 小顶堆,其堆顶元素(即根结点)的值最大 / 最小。 -## 堆常用操作 +## 8.1.2. 堆常用操作 值得说明的是,多数编程语言提供的是「优先队列 Priority Queue」,其是一种抽象数据结构,**定义为具有出队优先级的队列**。 @@ -203,7 +205,7 @@ comments: true // Swift 未提供内置 heap 类 ``` -## 堆的实现 +## 8.1.3. 堆的实现 下文实现的是「大顶堆」,若想转换为「小顶堆」,将所有大小逻辑判断取逆(例如将 $\geq$ 替换为 $\leq$ )即可,有兴趣的同学可自行实现。 @@ -215,7 +217,9 @@ comments: true 具体地,给定索引 $i$ ,那么其左子结点索引为 $2i + 1$ 、右子结点索引为 $2i + 2$ 、父结点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空结点或结点不存在。 -![representation_of_heap](heap.assets/representation_of_heap.png) + 我们将索引映射公式封装成函数,以便后续使用。 @@ -414,22 +418,24 @@ comments: true 考虑从入堆结点开始,**从底至顶执行堆化**。具体地,比较插入结点与其父结点的值,若插入结点更大则将它们交换;并循环以上操作,从底至顶地修复堆中的各个结点;直至越过根结点时结束,或当遇到无需交换的结点时提前结束。 === "Step 1" - ![heap_push_step1](heap.assets/heap_push_step1.png) + === "Step 2" - ![heap_push_step2](heap.assets/heap_push_step2.png) + ![heap_push_step2](heap.assets/heap_push_step2.png){ width="600" } === "Step 3" - ![heap_push_step3](heap.assets/heap_push_step3.png) + ![heap_push_step3](heap.assets/heap_push_step3.png){ width="600" } === "Step 4" - ![heap_push_step4](heap.assets/heap_push_step4.png) + ![heap_push_step4](heap.assets/heap_push_step4.png){ width="600" } === "Step 5" - ![heap_push_step5](heap.assets/heap_push_step5.png) + ![heap_push_step5](heap.assets/heap_push_step5.png){ width="600" } === "Step 6" - ![heap_push_step6](heap.assets/heap_push_step6.png) + ![heap_push_step6](heap.assets/heap_push_step6.png){ width="600" } 设结点总数为 $n$ ,则树的高度为 $O(\log n)$ ,易得堆化操作的循环轮数最多为 $O(\log n)$ ,**因而元素入堆操作的时间复杂度为 $O(\log n)$** 。 @@ -564,34 +570,34 @@ comments: true 顾名思义,**从顶至底堆化的操作方向与从底至顶堆化相反**,我们比较根结点的值与其两个子结点的值,将最大的子结点与根结点执行交换,并循环以上操作,直到越过叶结点时结束,或当遇到无需交换的结点时提前结束。 === "Step 1" - ![heap_poll_step1](heap.assets/heap_poll_step1.png) + ![heap_poll_step1](heap.assets/heap_poll_step1.png){ width="600" } === "Step 2" - ![heap_poll_step2](heap.assets/heap_poll_step2.png) + ![heap_poll_step2](heap.assets/heap_poll_step2.png){ width="600" } === "Step 3" - ![heap_poll_step3](heap.assets/heap_poll_step3.png) + ![heap_poll_step3](heap.assets/heap_poll_step3.png){ width="600" } === "Step 4" - ![heap_poll_step4](heap.assets/heap_poll_step4.png) + ![heap_poll_step4](heap.assets/heap_poll_step4.png){ width="600" } === "Step 5" - ![heap_poll_step5](heap.assets/heap_poll_step5.png) + ![heap_poll_step5](heap.assets/heap_poll_step5.png){ width="600" } === "Step 6" - ![heap_poll_step6](heap.assets/heap_poll_step6.png) + ![heap_poll_step6](heap.assets/heap_poll_step6.png){ width="600" } === "Step 7" - ![heap_poll_step7](heap.assets/heap_poll_step7.png) + ![heap_poll_step7](heap.assets/heap_poll_step7.png){ width="600" } === "Step 8" - ![heap_poll_step8](heap.assets/heap_poll_step8.png) + ![heap_poll_step8](heap.assets/heap_poll_step8.png){ width="600" } === "Step 9" - ![heap_poll_step9](heap.assets/heap_poll_step9.png) + ![heap_poll_step9](heap.assets/heap_poll_step9.png){ width="600" } === "Step 10" - ![heap_poll_step10](heap.assets/heap_poll_step10.png) + ![heap_poll_step10](heap.assets/heap_poll_step10.png){ width="600" } 与元素入堆操作类似,**堆顶元素出堆操作的时间复杂度为 $O(\log n)$** 。 @@ -856,7 +862,7 @@ $$ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1 $$ -![heapify_count](heap.assets/heapify_count.png) +![heapify_count](heap.assets/heapify_count.png){ width="600" } 化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,易得 @@ -885,7 +891,7 @@ $$ 进一步地,高度为 $h$ 的完美二叉树的结点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。 -## 堆常见应用 +## 8.1.4. 堆常见应用 - **优先队列**。堆常作为实现优先队列的首选数据结构,入队和出队操作时间复杂度为 $O(\log n)$ ,建队操作为 $O(n)$ ,皆非常高效。 - **堆排序**。给定一组数据,我们使用其建堆,并依次全部弹出,则可以得到有序的序列。当然,堆排序一般无需弹出元素,仅需每轮将堆顶元素交换至数组尾部并减小堆的长度即可。 diff --git a/docs/chapter_introduction/algorithms_are_everywhere.md b/docs/chapter_introduction/algorithms_are_everywhere.md index 5d80f9c92..980fba81e 100644 --- a/docs/chapter_introduction/algorithms_are_everywhere.md +++ b/docs/chapter_introduction/algorithms_are_everywhere.md @@ -2,7 +2,7 @@ comments: true --- -# 算法无处不在 +# 1.1. 算法无处不在 听到“算法”这个词,我们一般会联想到数学。但实际上,大多数算法并不包含复杂的数学,而更像是在考察基本逻辑,而这些逻辑在我们日常生活中处处可见。 diff --git a/docs/chapter_introduction/what_is_dsa.md b/docs/chapter_introduction/what_is_dsa.md index 10ea25133..d104e4852 100644 --- a/docs/chapter_introduction/what_is_dsa.md +++ b/docs/chapter_introduction/what_is_dsa.md @@ -2,9 +2,9 @@ comments: true --- -# 算法是什么 +# 1.2. 算法是什么 -## 算法定义 +## 1.2.1. 算法定义 「算法 Algorithm」是在有限时间内解决特定问题的一组指令或操作步骤。算法具有以下特性: @@ -13,7 +13,7 @@ comments: true - 具有可行性,可在有限步骤、有限时间、有限内存空间下完成。 - 独立于编程语言,即可用多种语言实现。 -## 数据结构定义 +## 1.2.2. 数据结构定义 「数据结构 Data Structure」是在计算机中组织与存储数据的方式。为了提高数据存储和操作性能,数据结构的设计原则有: @@ -23,7 +23,7 @@ comments: true 数据结构的设计是一个充满权衡的过程,这意味着如果获得某方面的优势,则往往需要在另一方面做出妥协。例如,链表相对于数组,数据添加删除操作更加方便,但牺牲了数据的访问速度;图相对于链表,提供了更多的逻辑信息,但需要占用更多的内存空间。 -## 数据结构与算法的关系 +## 1.2.3. 数据结构与算法的关系 「数据结构」与「算法」是高度相关、紧密嵌合的,体现在: diff --git a/docs/chapter_preface/about_the_book.md b/docs/chapter_preface/about_the_book.md index 78133f7b2..3d2973f8c 100644 --- a/docs/chapter_preface/about_the_book.md +++ b/docs/chapter_preface/about_the_book.md @@ -2,7 +2,7 @@ comments: true --- -# 关于本书 +# 0.1. 关于本书 五年前发生的一件事,成为了我职业生涯的重要转折点。当时的我在交大读研,对互联网求职一无所知,但仍然硬着头皮申请了 Microsoft 软件工程师实习。面试官让我在白板上写出“快速排序”代码,我畏畏缩缩地写了一个“冒泡排序”,并且还写错了` (ToT) ` 。从面试官的表情上,我看到了一个大大的 "GG" 。 @@ -12,7 +12,7 @@ comments: trueFig. 冒泡操作
-## 算法流程 +## 11.2.1. 算法流程 1. 设数组长度为 $n$ ,完成第一轮「冒泡」后,数组最大元素已在正确位置,接下来只需排序剩余 $n - 1$ 个元素。 2. 同理,对剩余 $n - 1$ 个元素执行「冒泡」,可将第二大元素交换至正确位置,因而待排序元素只剩 $n - 2$ 个。 @@ -232,7 +232,7 @@ comments: true } ``` -## 算法特性 +## 11.2.2. 算法特性 **时间复杂度 $O(n^2)$** :各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 @@ -244,7 +244,7 @@ comments: true **自适应排序**:引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。 -## 效率优化 +## 11.2.3. 效率优化 我们发现,若在某轮「冒泡」中未执行任何交换操作,则说明数组已经完成排序,可直接返回结果。考虑可以增加一个标志位 `flag` 来监听该情况,若出现则直接返回。 diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md index 28421b7a3..c44dc9254 100644 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -2,7 +2,7 @@ comments: true --- -# 插入排序 +# 11.3. 插入排序 「插入排序 Insertion Sort」是一种基于 **数组插入操作** 的排序算法。 @@ -14,7 +14,7 @@ comments: trueFig. 插入操作
-## 算法流程 +## 11.3.1. 算法流程 1. 第 1 轮先选取数组的 **第 2 个元素** 为 `base` ,执行「插入操作」后, **数组前 2 个元素已完成排序**。 2. 第 2 轮选取 **第 3 个元素** 为 `base` ,执行「插入操作」后, **数组前 3 个元素已完成排序**。 @@ -194,7 +194,7 @@ comments: true } ``` -## 算法特性 +## 11.3.2. 算法特性 **时间复杂度 $O(n^2)$** :最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 @@ -206,7 +206,7 @@ comments: true **自适应排序**:最佳情况下,时间复杂度为 $O(n)$ 。 -## 插入排序 vs 冒泡排序 +## 11.3.3. 插入排序 vs 冒泡排序 !!! question diff --git a/docs/chapter_sorting/intro_to_sort.md b/docs/chapter_sorting/intro_to_sort.md index 2a1c184e7..e4e1552ac 100644 --- a/docs/chapter_sorting/intro_to_sort.md +++ b/docs/chapter_sorting/intro_to_sort.md @@ -2,7 +2,7 @@ comments: true --- -# 排序简介 +# 11.1. 排序简介 「排序算法 Sorting Algorithm」使得列表中的所有元素按照从小到大的顺序排列。 @@ -13,7 +13,7 @@ comments: trueFig. 排序中的不同元素类型和判断规则
-## 评价维度 +## 11.1.1. 评价维度 排序算法主要可根据 **稳定性 、就地性 、自适应性 、比较类** 来分类。 @@ -25,22 +25,22 @@ comments: true 假设我们有一个存储学生信息的表格,第 1, 2 列分别是姓名和年龄。那么在以下示例中,「非稳定排序」会导致输入数据的有序性丢失。因此「稳定排序」是很好的特性,**在多级排序中是必须的**。 ```shell -# 输入数据是按照姓名排序好的 -# (name, age) - ('A', 19) - ('B', 18) - ('C', 21) - ('D', 19) - ('E', 23) - -# 假设使用非稳定排序算法按年龄排序列表, -# 结果中 ('D', 19) 和 ('A', 19) 的相对位置改变, -# 输入数据按姓名排序的性质丢失 - ('B', 18) - ('D', 19) - ('A', 19) - ('C', 21) - ('E', 23) + # 输入数据是按照姓名排序好的 + # (name, age) + ('A', 19) + ('B', 18) + ('C', 21) + ('D', 19) + ('E', 23) + + # 假设使用非稳定排序算法按年龄排序列表, + # 结果中 ('D', 19) 和 ('A', 19) 的相对位置改变, + # 输入数据按姓名排序的性质丢失 + ('B', 18) + ('D', 19) + ('A', 19) + ('C', 21) + ('E', 23) ``` ### 就地性 @@ -64,7 +64,7 @@ comments: true 「比较类排序」的时间复杂度最优为 $O(n \log n)$ ;而「非比较类排序」可以达到 $O(n)$ 的时间复杂度,但通用性较差。 -## 理想排序算法 +## 11.1.2. 理想排序算法 - **运行快**,即时间复杂度低; - **稳定排序**,即排序后相等元素的相对位置不变化; diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index ad3b4f570..c0c0fd76d 100644 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -2,7 +2,7 @@ comments: true --- -# 归并排序 +# 11.5. 归并排序 「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并」两个阶段: @@ -13,7 +13,7 @@ comments: trueFig. 归并排序两阶段:划分与合并
-## 算法流程 +## 11.5.1. 算法流程 **「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组**,直至长度为 1 ; @@ -453,7 +453,7 @@ comments: true - `nums` 的待合并区间为 `[left, right]` ,而因为 `tmp` 只复制了 `nums` 该区间元素,所以 `tmp` 对应区间为 `[0, right - left]` ,**需要特别注意代码中各个变量的含义**。 - 判断 `tmp[i]` 和 `tmp[j]` 的大小的操作中,还 **需考虑当子数组遍历完成后的索引越界问题**,即 `i > leftEnd` 和 `j > rightEnd` 的情况,索引越界的优先级是最高的,例如如果左子数组已经被合并完了,那么不用继续判断,直接合并右子数组元素即可。 -## 算法特性 +## 11.5.2. 算法特性 - **时间复杂度 $O(n \log n)$** :划分形成高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,总体使用 $O(n \log n)$ 时间。 - **空间复杂度 $O(n)$** :需借助辅助数组实现合并,使用 $O(n)$ 大小的额外空间;递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。 @@ -461,7 +461,7 @@ comments: true - **稳定排序**:在合并时可保证相等元素的相对位置不变。 - **非自适应排序**:对于任意输入数据,归并排序的时间复杂度皆相同。 -## 链表排序 * +## 11.5.3. 链表排序 * 归并排序有一个很特别的优势,用于排序链表时有很好的性能表现,**空间复杂度可被优化至 $O(1)$** ,这是因为: diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index 20c26609c..f804c2105 100644 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -2,7 +2,7 @@ comments: true --- -# 快速排序 +# 11.4. 快速排序 「快速排序 Quick Sort」是一种基于“分治思想”的排序算法,速度很快、应用很广。 @@ -263,7 +263,7 @@ comments: true 哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题**。 -## 算法流程 +## 11.4.1. 算法流程 1. 首先,对数组执行一次「哨兵划分」,得到待排序的 **左子数组** 和 **右子数组**; 2. 接下来,对 **左子数组** 和 **右子数组** 分别 **递归执行**「哨兵划分」…… @@ -412,7 +412,7 @@ comments: true } ``` -## 算法特性 +## 11.4.2. 算法特性 **平均时间复杂度 $O(n \log n)$** :平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 @@ -426,7 +426,7 @@ comments: true **自适应排序**:最差情况下,时间复杂度劣化至 $O(n^2)$ 。 -## 快排为什么快? +## 11.4.3. 快排为什么快? 从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 **效率更高**,这是因为: @@ -434,7 +434,7 @@ comments: true - **缓存使用效率高**:哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。 - **复杂度的常数系数低**:在提及的三种算法中,快速排序的 **比较**、**赋值**、**交换** 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。 -## 基准数优化 +## 11.4.4. 基准数优化 **普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 @@ -652,7 +652,7 @@ comments: true } ``` -## 尾递归优化 +## 11.4.5. 尾递归优化 **普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 diff --git a/docs/chapter_sorting/summary.md b/docs/chapter_sorting/summary.md index df0b7a700..71cc82609 100644 --- a/docs/chapter_sorting/summary.md +++ b/docs/chapter_sorting/summary.md @@ -2,5 +2,5 @@ comments: true --- -# 小结 +# 11.6. 小结 diff --git a/docs/chapter_stack_and_queue/deque.md b/docs/chapter_stack_and_queue/deque.md index 2d0aca9e8..955593cbc 100644 --- a/docs/chapter_stack_and_queue/deque.md +++ b/docs/chapter_stack_and_queue/deque.md @@ -2,7 +2,7 @@ comments: true --- -# 双向队列 +# 5.3. 双向队列 对于队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。 @@ -10,7 +10,7 @@ comments: trueFig. 双向队列的操作
-## 双向队列常用操作 +## 5.3.1. 双向队列常用操作 双向队列的常用操作见下表(方法命名以 Java 为例)。 @@ -223,7 +223,7 @@ comments: true let isEmpty = deque.isEmpty ``` -## 双向队列实现 +## 5.3.2. 双向队列实现 双向队列需要一种可以在两端添加、两端删除的数据结构。与队列的实现方法类似,双向队列也可以使用双向链表和循环数组来实现。 diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index 530d16a48..34b6c0121 100644 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -2,7 +2,7 @@ comments: true --- -# 队列 +# 5.2. 队列 「队列 Queue」是一种遵循「先入先出 first in, first out」数据操作规则的线性数据结构。顾名思义,队列模拟的是排队现象,即外面的人不断加入队列尾部,而处于队列头部的人不断地离开。 @@ -12,7 +12,7 @@ comments: trueFig. 队列的先入先出特性
-## 队列常用操作 +## 5.2.1. 队列常用操作 队列的常用操作见下表(方法命名以 Java 为例)。 @@ -256,7 +256,7 @@ comments: true let isEmpty = queue.isEmpty ``` -## 队列实现 +## 5.2.2. 队列实现 队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。 @@ -1202,11 +1202,11 @@ comments: true 以上代码仍存在局限性,即长度不可变。然而,我们可以通过将数组替换为列表(即动态数组)来引入扩容机制,有兴趣的同学可以尝试实现。 -## 两种实现对比 +## 5.2.3. 两种实现对比 与栈的结论一致,在此不再赘述。 -## 队列典型应用 +## 5.2.4. 队列典型应用 - **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 - **各种待办事项**。例如打印机的任务队列、餐厅的出餐队列等等。 diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index dd45821cb..d19c5760b 100644 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -2,7 +2,7 @@ comments: true --- -# 栈 +# 5.1. 栈 「栈 Stack」是一种遵循「先入后出 first in, last out」数据操作规则的线性数据结构。我们可以将栈类比为放在桌面上的一摞盘子,如果需要拿出底部的盘子,则需要先将上面的盘子依次取出。 @@ -14,7 +14,7 @@ comments: trueFig. 栈的先入后出特性
-## 栈常用操作 +## 5.1.1. 栈常用操作 栈的常用操作见下表(方法命名以 Java 为例)。 @@ -255,7 +255,7 @@ comments: true let isEmpty = stack.isEmpty ``` -## 栈的实现 +## 5.1.2. 栈的实现 为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。 @@ -1023,7 +1023,7 @@ comments: true } ``` -## 两种实现对比 +## 5.1.3. 两种实现对比 ### 支持操作 @@ -1048,7 +1048,7 @@ comments: true 综上,我们不能简单地确定哪种实现更加省内存,需要 case-by-case 地分析。 -## 栈典型应用 +## 5.1.4. 栈典型应用 - **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就将上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 - **程序内存管理**。每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。 diff --git a/docs/chapter_stack_and_queue/summary.md b/docs/chapter_stack_and_queue/summary.md index d6066f0f1..19cf6c8c3 100644 --- a/docs/chapter_stack_and_queue/summary.md +++ b/docs/chapter_stack_and_queue/summary.md @@ -2,7 +2,7 @@ comments: true --- -# 小结 +# 5.4. 小结 - 栈是一种遵循先入后出的数据结构,可以使用数组或链表实现。 - 在时间效率方面,栈的数组实现具有更好的平均效率,但扩容时会导致单次入栈操作的时间复杂度劣化至 $O(n)$ 。相对地,栈的链表实现具有更加稳定的效率表现。 diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index 95c818c0c..bec639d68 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -2,7 +2,7 @@ comments: true --- -# AVL 树 * +# 7.4. AVL 树 * 在「二叉搜索树」章节中提到,在进行多次插入与删除操作后,二叉搜索树可能会退化为链表。此时所有操作的时间复杂度都会由 $O(\log n)$ 劣化至 $O(n)$ 。 @@ -18,7 +18,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit 换言之,在频繁增删查改的使用场景中,AVL 树可始终保持很高的数据增删查改效率,具有很好的应用价值。 -## AVL 树常见术语 +## 7.4.1. AVL 树常见术语 「AVL 树」既是「二叉搜索树」又是「平衡二叉树」,同时满足这两种二叉树的所有性质,因此又被称为「平衡二叉搜索树」。 @@ -329,7 +329,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit 设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。 -## AVL 树旋转 +## 7.4.2. AVL 树旋转 AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡**。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 @@ -827,7 +827,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 } ``` -## AVL 树常用操作 +## 7.4.3. AVL 树常用操作 ### 插入结点 @@ -1253,7 +1253,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 「AVL 树」的结点查找操作与「二叉搜索树」一致,在此不再赘述。 -## AVL 树典型应用 +## 7.4.4. AVL 树典型应用 - 组织存储大型数据,适用于高频查找、低频增删场景; - 用于建立数据库中的索引系统; diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index 4c6269bbf..e9fb1d8c9 100644 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -2,7 +2,7 @@ comments: true --- -# 二叉搜索树 +# 7.3. 二叉搜索树 「二叉搜索树 Binary Search Tree」满足以下条件: @@ -11,7 +11,7 @@ comments: true ![binary_search_tree](binary_search_tree.assets/binary_search_tree.png) -## 二叉搜索树的操作 +## 7.3.1. 二叉搜索树的操作 ### 查找结点 @@ -934,7 +934,7 @@ comments: true } ``` -## 二叉搜索树的效率 +## 7.3.2. 二叉搜索树的效率 假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为: @@ -963,7 +963,7 @@ comments: true -## 二叉搜索树的退化 +## 7.3.3. 二叉搜索树的退化 理想情况下,我们希望二叉搜索树的是“左右平衡”的(详见「平衡二叉树」章节),此时可以在 $\log n$ 轮循环内查找任意结点。 @@ -975,7 +975,7 @@ comments: true ![bst_degradation](binary_search_tree.assets/bst_degradation.png) -## 二叉搜索树常见应用 +## 7.3.4. 二叉搜索树常见应用 - 系统中的多级索引,高效查找、插入、删除操作。 - 各种搜索算法的底层数据结构。 diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 8b51788d2..66a80a791 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -2,7 +2,7 @@ comments: true --- -# 二叉树 +# 7.1. 二叉树 「二叉树 Binary Tree」是一种非线性数据结构,代表着祖先与后代之间的派生关系,体现着“一分为二”的分治逻辑。类似于链表,二叉树也是以结点为单位存储的,结点包含「值」和两个「指针」。 @@ -129,7 +129,7 @@ comments: trueFig. 子结点与子树
-## 二叉树常见术语 +## 7.1.1. 二叉树常见术语 二叉树的术语较多,建议尽量理解并记住。后续可能遗忘,可以在需要使用时回来查看确认。 @@ -150,7 +150,7 @@ comments: true 值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。 -## 二叉树基本操作 +## 7.1.2. 二叉树基本操作 **初始化二叉树**。与链表类似,先初始化结点,再构建引用指向(即指针)。 @@ -404,7 +404,7 @@ comments: true 插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 -## 常见二叉树类型 +## 7.1.3. 常见二叉树类型 ### 完美二叉树 @@ -436,7 +436,7 @@ comments: true ![balanced_binary_tree](binary_tree.assets/balanced_binary_tree.png) -## 二叉树的退化 +## 7.1.4. 二叉树的退化 当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。 @@ -460,7 +460,7 @@ comments: true -## 二叉树表示方式 * +## 7.1.5. 二叉树表示方式 * 我们一般使用二叉树的「链表表示」,即存储单位为结点 `TreeNode` ,结点之间通过指针(引用)相连接。本文前述示例代码展示了二叉树在链表表示下的各项基本操作。 diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index 3070097a1..aa727c431 100644 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -2,11 +2,11 @@ comments: true --- -# 二叉树遍历 +# 7.2. 二叉树遍历 非线性数据结构的遍历操作比线性数据结构更加复杂,往往需要使用搜索算法来实现。常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。 -## 层序遍历 +## 7.2.1. 层序遍历 「层序遍历 Hierarchical-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。 @@ -208,7 +208,7 @@ comments: true } ``` -## 前序、中序、后序遍历 +## 7.2.2. 前序、中序、后序遍历 相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。 diff --git a/docs/chapter_tree/summary.md b/docs/chapter_tree/summary.md index 15dc2f6d3..8566eaa8e 100644 --- a/docs/chapter_tree/summary.md +++ b/docs/chapter_tree/summary.md @@ -2,7 +2,7 @@ comments: true --- -# 小结 +# 7.5. 小结 - 二叉树是一种非线性数据结构,代表着“一分为二”的分治逻辑。二叉树的结点包含「值」和两个「指针」,分别指向左子结点和右子结点。 - 选定二叉树中某结点,将其左(右)子结点以下形成的树称为左(右)子树。 diff --git a/mkdocs.yml b/mkdocs.yml index a091bdd7f..7ec072cd7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -123,60 +123,60 @@ extra_css: # Page tree nav: - - 写在前面: - - 关于本书: chapter_preface/about_the_book.md - - 如何使用本书: chapter_preface/suggestions.md - - 编程环境安装: chapter_preface/installation.md - - 一起参与创作: chapter_preface/contribution.md - - 引言: - - 算法无处不在: chapter_introduction/algorithms_are_everywhere.md - - 算法是什么: chapter_introduction/what_is_dsa.md - - 计算复杂度: - - 算法效率评估: chapter_computational_complexity/performance_evaluation.md - - 时间复杂度: chapter_computational_complexity/time_complexity.md - - 空间复杂度: chapter_computational_complexity/space_complexity.md - - 权衡时间与空间: chapter_computational_complexity/space_time_tradeoff.md - - 小结: chapter_computational_complexity/summary.md - - 数据结构简介: - - 数据与内存: chapter_data_structure/data_and_memory.md - - 数据结构分类: chapter_data_structure/classification_of_data_structure.md - - 小结: chapter_data_structure/summary.md - - 数组与链表: - - 数组(Array): chapter_array_and_linkedlist/array.md - - 链表(LinkedList): chapter_array_and_linkedlist/linked_list.md - - 列表(List): chapter_array_and_linkedlist/list.md - - 小结: chapter_array_and_linkedlist/summary.md - - 栈与队列: - - 栈(Stack): chapter_stack_and_queue/stack.md - - 队列(Queue): chapter_stack_and_queue/queue.md - - 双向队列(Deque): chapter_stack_and_queue/deque.md - - 小结: chapter_stack_and_queue/summary.md - - 散列表: - - 哈希表(HashMap): chapter_hashing/hash_map.md - - 哈希冲突处理: chapter_hashing/hash_collision.md - - 小结: chapter_hashing/summary.md - - 二叉树: - - 二叉树(Binary Tree): chapter_tree/binary_tree.md - - 二叉树遍历: chapter_tree/binary_tree_traversal.md - - 二叉搜索树: chapter_tree/binary_search_tree.md - - AVL 树 *: chapter_tree/avl_tree.md - - 小结: chapter_tree/summary.md - - 堆: - - 堆(Heap): chapter_heap/heap.md - - 图: - - 图(Graph): chapter_graph/graph.md - - 图基础操作: chapter_graph/basic_operation_of_graph.md - - 查找算法: - - 线性查找: chapter_searching/linear_search.md - - 二分查找: chapter_searching/binary_search.md - - 哈希查找: chapter_searching/hashing_search.md - - 小结: chapter_searching/summary.md - - 排序算法: - - 排序简介: chapter_sorting/intro_to_sort.md - - 冒泡排序: chapter_sorting/bubble_sort.md - - 插入排序: chapter_sorting/insertion_sort.md - - 快速排序: chapter_sorting/quick_sort.md - - 归并排序: chapter_sorting/merge_sort.md - - 小结: chapter_sorting/summary.md + - 0. 写在前面: + - 0.1. 关于本书: chapter_preface/about_the_book.md + - 0.2. 如何使用本书: chapter_preface/suggestions.md + - 0.3. 编程环境安装: chapter_preface/installation.md + - 0.4. 一起参与创作: chapter_preface/contribution.md + - 1. 引言: + - 1.1. 算法无处不在: chapter_introduction/algorithms_are_everywhere.md + - 1.2. 算法是什么: chapter_introduction/what_is_dsa.md + - 2. 计算复杂度: + - 2.1. 算法效率评估: chapter_computational_complexity/performance_evaluation.md + - 2.2. 时间复杂度: chapter_computational_complexity/time_complexity.md + - 2.3. 空间复杂度: chapter_computational_complexity/space_complexity.md + - 2.4. 权衡时间与空间: chapter_computational_complexity/space_time_tradeoff.md + - 2.5. 小结: chapter_computational_complexity/summary.md + - 3. 数据结构简介: + - 3.1. 数据与内存: chapter_data_structure/data_and_memory.md + - 3.2. 数据结构分类: chapter_data_structure/classification_of_data_structure.md + - 3.3. 小结: chapter_data_structure/summary.md + - 4. 数组与链表: + - 4.1. 数组(Array): chapter_array_and_linkedlist/array.md + - 4.2. 链表(LinkedList): chapter_array_and_linkedlist/linked_list.md + - 4.3. 列表(List): chapter_array_and_linkedlist/list.md + - 4.4. 小结: chapter_array_and_linkedlist/summary.md + - 5. 栈与队列: + - 5.1. 栈(Stack): chapter_stack_and_queue/stack.md + - 5.2. 队列(Queue): chapter_stack_and_queue/queue.md + - 5.3. 双向队列(Deque): chapter_stack_and_queue/deque.md + - 5.4. 小结: chapter_stack_and_queue/summary.md + - 6. 散列表: + - 6.1. 哈希表(HashMap): chapter_hashing/hash_map.md + - 6.2. 哈希冲突处理: chapter_hashing/hash_collision.md + - 6.3. 小结: chapter_hashing/summary.md + - 7. 二叉树: + - 7.1. 二叉树(Binary Tree): chapter_tree/binary_tree.md + - 7.2. 二叉树遍历: chapter_tree/binary_tree_traversal.md + - 7.3. 二叉搜索树: chapter_tree/binary_search_tree.md + - 7.4. AVL 树 *: chapter_tree/avl_tree.md + - 7.5. 小结: chapter_tree/summary.md + - 8. 堆: + - 8.1. 堆(Heap): chapter_heap/heap.md + - 9. 图: + - 9.1. 图(Graph): chapter_graph/graph.md + - 9.2. 图基础操作: chapter_graph/basic_operation_of_graph.md + - 10. 查找算法: + - 10.1. 线性查找: chapter_searching/linear_search.md + - 10.2. 二分查找: chapter_searching/binary_search.md + - 10.3. 哈希查找: chapter_searching/hashing_search.md + - 10.4. 小结: chapter_searching/summary.md + - 11. 排序算法: + - 11.1. 排序简介: chapter_sorting/intro_to_sort.md + - 11.2. 冒泡排序: chapter_sorting/bubble_sort.md + - 11.3. 插入排序: chapter_sorting/insertion_sort.md + - 11.4. 快速排序: chapter_sorting/quick_sort.md + - 11.5. 归并排序: chapter_sorting/merge_sort.md + - 11.6. 小结: chapter_sorting/summary.md - 参考文献: - chapter_reference/index.md