diff --git a/chapter_array_and_linkedlist/array.md b/chapter_array_and_linkedlist/array.md index ed7de2780..cf75c4e53 100755 --- a/chapter_array_and_linkedlist/array.md +++ b/chapter_array_and_linkedlist/array.md @@ -2,7 +2,7 @@ comments: true --- -# 4.1. 数组 +# 4.1. 数组 「数组 Array」是一种将 **相同类型元素** 存储在 **连续内存空间** 的数据结构,将元素在数组中的位置称为元素的「索引 Index」。 @@ -102,7 +102,7 @@ comments: true var nums = [_]i32{ 1, 3, 2, 5, 4 }; ``` -## 4.1.1. 数组优点 +## 4.1.1. 数组优点 **在数组中访问元素非常高效**。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。 @@ -244,7 +244,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -## 4.1.2. 数组缺点 +## 4.1.2. 数组缺点 **数组在初始化后长度不可变**。由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。 @@ -624,7 +624,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -## 4.1.3. 数组常用操作 +## 4.1.3. 数组常用操作 **数组遍历**。以下介绍两种常用的遍历方法。 @@ -914,7 +914,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -## 4.1.4. 数组典型应用 +## 4.1.4. 数组典型应用 **随机访问**。如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。 diff --git a/chapter_array_and_linkedlist/linked_list.md b/chapter_array_and_linkedlist/linked_list.md index 6a7bd9ec9..533701130 100755 --- a/chapter_array_and_linkedlist/linked_list.md +++ b/chapter_array_and_linkedlist/linked_list.md @@ -2,7 +2,7 @@ comments: true --- -# 4.2. 链表 +# 4.2. 链表 !!! note "引言" @@ -314,7 +314,7 @@ comments: true n3.next = &n4; ``` -## 4.2.1. 链表优点 +## 4.2.1. 链表优点 **在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 @@ -524,7 +524,7 @@ comments: true } ``` -## 4.2.2. 链表缺点 +## 4.2.2. 链表缺点 **链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 @@ -668,7 +668,7 @@ comments: true **链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 -## 4.2.3. 链表常用操作 +## 4.2.3. 链表常用操作 **遍历链表查找**。遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。 @@ -827,7 +827,7 @@ comments: true } ``` -## 4.2.4. 常见链表类型 +## 4.2.4. 常见链表类型 **单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。 diff --git a/chapter_array_and_linkedlist/list.md b/chapter_array_and_linkedlist/list.md index 421436e82..2bd56684b 100755 --- a/chapter_array_and_linkedlist/list.md +++ b/chapter_array_and_linkedlist/list.md @@ -2,13 +2,13 @@ comments: true --- -# 4.3. 列表 +# 4.3. 列表 **由于长度不可变,数组的实用性大大降低**。在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。 为了解决此问题,诞生了一种被称为「列表 List」的数据结构。列表可以被理解为长度可变的数组,因此也常被称为「动态数组 Dynamic Array」。列表基于数组实现,继承了数组的优点,同时还可以在程序运行中实时扩容。在列表中,我们可以自由地添加元素,而不用担心超过容量限制。 -## 4.3.1. 列表常用操作 +## 4.3.1. 列表常用操作 **初始化列表**。我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。 @@ -703,7 +703,7 @@ comments: true std.sort.sort(i32, list.items, {}, comptime std.sort.asc(i32)); ``` -## 4.3.2. 列表简易实现 * +## 4.3.2. 列表简易实现 * 为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点: diff --git a/chapter_array_and_linkedlist/summary.md b/chapter_array_and_linkedlist/summary.md index 3b6ac2cb0..22a304dda 100644 --- a/chapter_array_and_linkedlist/summary.md +++ b/chapter_array_and_linkedlist/summary.md @@ -2,14 +2,14 @@ comments: true --- -# 4.4. 小结 +# 4.4. 小结 - 数组和链表是两种基本数据结构,代表了数据在计算机内存中的两种存储方式,即连续空间存储和离散空间存储。两者的优点与缺点呈现出此消彼长的关系。 - 数组支持随机访问、内存空间占用小;但插入与删除元素效率低,且初始化后长度不可变。 - 链表可通过更改指针实现高效的结点插入与删除,并且可以灵活地修改长度;但结点访问效率低、占用内存多。常见的链表类型有单向链表、循环链表、双向链表。 - 列表又称动态数组,是基于数组实现的一种数据结构,其保存了数组的优势,且可以灵活改变长度。列表的出现大大提升了数组的实用性,但副作用是会造成部分内存空间浪费。 -## 4.4.1. 数组 VS 链表 +## 4.4.1. 数组 VS 链表
Table. 数组与链表特点对比
diff --git a/chapter_computational_complexity/performance_evaluation.md b/chapter_computational_complexity/performance_evaluation.md index 45288720f..056a7bb71 100644 --- a/chapter_computational_complexity/performance_evaluation.md +++ b/chapter_computational_complexity/performance_evaluation.md @@ -2,9 +2,9 @@ comments: true --- -# 2.1. 算法效率评估 +# 2.1. 算法效率评估 -## 2.1.1. 算法评价维度 +## 2.1.1. 算法评价维度 在开始学习算法之前,我们首先要想清楚算法的设计目标是什么,或者说,如何来评判算法的好与坏。整体上看,我们设计算法时追求两个层面的目标。 @@ -18,7 +18,7 @@ comments: true 数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。 -## 2.1.2. 效率评估方法 +## 2.1.2. 效率评估方法 ### 实际测试 @@ -42,7 +42,7 @@ comments: true 如果感觉对复杂度分析的概念一知半解,无需担心,后续章节会展开介绍。 -## 2.1.3. 复杂度分析重要性 +## 2.1.3. 复杂度分析重要性 复杂度分析给出一把评价算法效率的“标尺”,告诉我们执行某个算法需要多少时间和空间资源,也让我们可以开展不同算法之间的效率对比。 diff --git a/chapter_computational_complexity/space_complexity.md b/chapter_computational_complexity/space_complexity.md index 1c31fffd7..b8e35f606 100755 --- a/chapter_computational_complexity/space_complexity.md +++ b/chapter_computational_complexity/space_complexity.md @@ -2,11 +2,11 @@ comments: true --- -# 2.3. 空间复杂度 +# 2.3. 空间复杂度 「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势**。这个概念与时间复杂度很类似。 -## 2.3.1. 算法相关空间 +## 2.3.1. 算法相关空间 算法运行中,使用的内存空间主要有以下几种: @@ -252,7 +252,7 @@ comments: true ``` -## 2.3.2. 推算方法 +## 2.3.2. 推算方法 空间复杂度的推算方法和时间复杂度总体类似,只是从统计“计算操作数量”变为统计“使用空间大小”。与时间复杂度不同的是,**我们一般只关注「最差空间复杂度」**。这是因为内存空间是一个硬性要求,我们必须保证在所有输入数据下都有足够的内存空间预留。 @@ -554,7 +554,7 @@ comments: true ``` -## 2.3.3. 常见类型 +## 2.3.3. 常见类型 设输入数据大小为 $n$ ,常见的空间复杂度类型有(从低到高排列) diff --git a/chapter_computational_complexity/space_time_tradeoff.md b/chapter_computational_complexity/space_time_tradeoff.md index ea4e7c79c..29be8c329 100755 --- a/chapter_computational_complexity/space_time_tradeoff.md +++ b/chapter_computational_complexity/space_time_tradeoff.md @@ -2,7 +2,7 @@ comments: true --- -# 2.4. 权衡时间与空间 +# 2.4. 权衡时间与空间 理想情况下,我们希望算法的时间复杂度和空间复杂度都能够达到最优,而实际上,同时优化时间复杂度和空间复杂度是非常困难的。 @@ -10,7 +10,7 @@ comments: true 大多数情况下,时间都是比空间更宝贵的,只要空间复杂度不要太离谱、能接受就行,**因此以空间换时间最为常用**。 -## 2.4.1. 示例题目 * +## 2.4.1. 示例题目 * 以 LeetCode 全站第一题 [两数之和](https://leetcode.cn/problems/two-sum/) 为例。 diff --git a/chapter_computational_complexity/summary.md b/chapter_computational_complexity/summary.md index acd745f9f..6f14b0182 100644 --- a/chapter_computational_complexity/summary.md +++ b/chapter_computational_complexity/summary.md @@ -2,7 +2,7 @@ comments: true --- -# 2.5. 小结 +# 2.5. 小结 ### 算法效率评估 diff --git a/chapter_computational_complexity/time_complexity.md b/chapter_computational_complexity/time_complexity.md index aeacab92f..f1485a602 100755 --- a/chapter_computational_complexity/time_complexity.md +++ b/chapter_computational_complexity/time_complexity.md @@ -2,9 +2,9 @@ comments: true --- -# 2.2. 时间复杂度 +# 2.2. 时间复杂度 -## 2.2.1. 统计算法运行时间 +## 2.2.1. 统计算法运行时间 运行时间能够直观且准确地体现出算法的效率水平。如果我们想要 **准确预估一段代码的运行时间** ,该如何做呢? @@ -161,7 +161,7 @@ $$ 但实际上, **统计算法的运行时间既不合理也不现实**。首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。 -## 2.2.2. 统计时间增长趋势 +## 2.2.2. 统计时间增长趋势 「时间复杂度分析」采取了不同的做法,其统计的不是算法运行时间,而是 **算法运行时间随着数据量变大时的增长趋势** 。 @@ -381,7 +381,7 @@ $$ **时间复杂度也存在一定的局限性**。比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。 -## 2.2.3. 函数渐近上界 +## 2.2.3. 函数渐近上界 设算法「计算操作数量」为 $T(n)$ ,其是一个关于输入数据大小 $n$ 的函数。例如,以下算法的操作数量为 @@ -548,7 +548,7 @@ $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得 渐近上界的数学味儿有点重,如果你感觉没有完全理解,无需担心,因为在实际使用中我们只需要会推算即可,数学意义可以慢慢领悟。 -## 2.2.4. 推算方法 +## 2.2.4. 推算方法 推算出 $f(n)$ 后,我们就得到时间复杂度 $O(f(n))$ 。那么,如何来确定渐近上界 $f(n)$ 呢?总体分为两步,首先「统计操作数量」,然后「判断渐近上界」。 @@ -767,7 +767,7 @@ $$ -## 2.2.5. 常见类型 +## 2.2.5. 常见类型 设输入数据大小为 $n$ ,常见的时间复杂度类型有(从低到高排列) @@ -2407,7 +2407,7 @@ $$Fig. 阶乘阶的时间复杂度
-## 2.2.6. 最差、最佳、平均时间复杂度 +## 2.2.6. 最差、最佳、平均时间复杂度 **某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关**。举一个例子,输入一个长度为 $n$ 数组 `nums` ,其中 `nums` 由从 $1$ 至 $n$ 的数字组成,但元素顺序是随机打乱的;算法的任务是返回元素 $1$ 的索引。我们可以得出以下结论: diff --git a/chapter_data_structure/classification_of_data_structure.md b/chapter_data_structure/classification_of_data_structure.md index 1b8d7b739..7fd2912de 100644 --- a/chapter_data_structure/classification_of_data_structure.md +++ b/chapter_data_structure/classification_of_data_structure.md @@ -2,11 +2,11 @@ comments: true --- -# 3.2. 数据结构分类 +# 3.2. 数据结构分类 数据结构主要可根据「逻辑结构」和「物理结构」两种角度进行分类。 -## 3.2.1. 逻辑结构:线性与非线性 +## 3.2.1. 逻辑结构:线性与非线性 **「逻辑结构」反映了数据之间的逻辑关系**。数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。 @@ -19,7 +19,7 @@ comments: trueFig. 线性与非线性数据结构
-## 3.2.2. 物理结构:连续与离散 +## 3.2.2. 物理结构:连续与离散 !!! note diff --git a/chapter_data_structure/data_and_memory.md b/chapter_data_structure/data_and_memory.md index d0005a24d..a3a0c2fdf 100644 --- a/chapter_data_structure/data_and_memory.md +++ b/chapter_data_structure/data_and_memory.md @@ -2,9 +2,9 @@ comments: true --- -# 3.1. 数据与内存 +# 3.1. 数据与内存 -## 3.1.1. 基本数据类型 +## 3.1.1. 基本数据类型 谈到计算机中的数据,我们能够想到文本、图片、视频、语音、3D 模型等等,这些数据虽然组织形式不同,但都是由各种基本数据类型构成的。 @@ -202,7 +202,7 @@ $$ ``` -## 3.1.2. 计算机内存 +## 3.1.2. 计算机内存 在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度较快,但容量较小(通常为 GB 级别)。 diff --git a/chapter_data_structure/summary.md b/chapter_data_structure/summary.md index 5340746a3..b6c52eee5 100644 --- a/chapter_data_structure/summary.md +++ b/chapter_data_structure/summary.md @@ -2,7 +2,7 @@ comments: true --- -# 3.3. 小结 +# 3.3. 小结 - 整数 byte, short, int, long 、浮点数 float, double 、字符 char 、布尔 boolean 是计算机中的基本数据类型,占用空间的大小决定了它们的取值范围。 - 在程序运行时,数据存储在计算机的内存中。内存中每块空间都有独立的内存地址,程序是通过内存地址来访问数据的。 diff --git a/chapter_graph/graph.md b/chapter_graph/graph.md index a6b71da66..4ee3c6c9c 100644 --- a/chapter_graph/graph.md +++ b/chapter_graph/graph.md @@ -2,7 +2,7 @@ comments: true --- -# 9.1. 图 +# 9.1. 图 「图 Graph」是一种非线性数据结构,由「顶点 Vertex」和「边 Edge」组成。我们可将图 $G$ 抽象地表示为一组顶点 $V$ 和一组边 $E$ 的集合。例如,以下表示一个包含 5 个顶点和 7 条边的图 @@ -18,7 +18,7 @@ $$ 那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看成一种从「链表」拓展而来的数据结构。**相比线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,也从而更为复杂**。 -## 9.1.1. 图常见类型 +## 9.1.1. 图常见类型 根据边是否有方向,分为「无向图 Undirected Graph」和「有向图 Directed Graph」。 @@ -38,13 +38,13 @@ $$ ![weighted_graph](graph.assets/weighted_graph.png) -## 9.1.2. 图常用术语 +## 9.1.2. 图常用术语 - 「邻接 Adjacency」:当两顶点之间有边相连时,称此两顶点“邻接”。 - 「路径 Path」:从顶点 A 到顶点 B 走过的边构成的序列,被称为从 A 到 B 的“路径”。 - 「度 Degree」表示一个顶点具有多少条边。对于有向图,「入度 In-Degree」表示有多少条边指向该顶点,「出度 Out-Degree」表示有多少条边从该顶点指出。 -## 9.1.3. 图的表示 +## 9.1.3. 图的表示 图的常用表示方法有「邻接矩阵」和「邻接表」。以下使用「无向图」来举例。 @@ -74,7 +74,7 @@ $$ 观察上图发现,**邻接表结构与哈希表「链地址法」非常相似,因此我们也可以用类似方法来优化效率**。比如,当链表较长时,可以把链表转化为「AVL 树」,从而将时间效率从 $O(n)$ 优化至 $O(\log n)$ ,还可以通过中序遍历获取有序序列;还可以将链表转化为 HashSet(即哈希表),将时间复杂度降低至 $O(1)$ 。 -## 9.1.4. 图常见应用 +## 9.1.4. 图常见应用 现实中的许多系统都可以使用图来建模,对应的待求解问题也可以被约化为图计算问题。 diff --git a/chapter_graph/graph_operations.md b/chapter_graph/graph_operations.md index fd069238a..73a456dc6 100644 --- a/chapter_graph/graph_operations.md +++ b/chapter_graph/graph_operations.md @@ -2,11 +2,11 @@ comments: true --- -# 9.2. 图基础操作 +# 9.2. 图基础操作 图的基础操作分为对「边」的操作和对「顶点」的操作,在「邻接矩阵」和「邻接表」这两种表示下的实现方式不同。 -## 9.2.1. 基于邻接矩阵的实现 +## 9.2.1. 基于邻接矩阵的实现 设图的顶点总数为 $n$ ,则有: @@ -775,7 +775,7 @@ comments: true ``` -## 9.2.2. 基于邻接表的实现 +## 9.2.2. 基于邻接表的实现 设图的顶点总数为 $n$ 、边总数为 $m$ ,则有: @@ -1451,7 +1451,7 @@ comments: true [class]{GraphAdjList}-[func]{} ``` -## 9.2.3. 效率对比 +## 9.2.3. 效率对比 设图中共有 $n$ 个顶点和 $m$ 条边,下表为邻接矩阵和邻接表的时间和空间效率对比。 diff --git a/chapter_graph/graph_traversal.md b/chapter_graph/graph_traversal.md index 3ee19c2ab..8d01e7edc 100644 --- a/chapter_graph/graph_traversal.md +++ b/chapter_graph/graph_traversal.md @@ -2,7 +2,7 @@ comments: true --- -# 9.3. 图的遍历 +# 9.3. 图的遍历 !!! note "图与树的关系" @@ -12,7 +12,7 @@ comments: true 类似地,图的遍历方式也分为两种,即「广度优先遍历 Breadth-First Traversal」和「深度优先遍历 Depth-First Travsersal」,也称「广度优先搜索 Breadth-First Search」和「深度优先搜索 Depth-First Search」,简称为 BFS 和 DFS 。 -## 9.3.1. 广度优先遍历 +## 9.3.1. 广度优先遍历 **广度优先遍历优是一种由近及远的遍历方式,从距离最近的顶点开始访问,并一层层向外扩张**。具体地,从某个顶点出发,先遍历该顶点的所有邻接顶点,随后遍历下个顶点的所有邻接顶点,以此类推…… @@ -200,7 +200,7 @@ BFS 常借助「队列」来实现。队列具有“先入先出”的性质, **空间复杂度:** 列表 `res` ,哈希表 `visited` ,队列 `que` 中的顶点数量最多为 $|V|$ ,使用 $O(|V|)$ 空间。 -## 9.3.2. 深度优先遍历 +## 9.3.2. 深度优先遍历 **深度优先遍历是一种优先走到底、无路可走再回头的遍历方式**。具体地,从某个顶点出发,不断地访问当前结点的某个邻接顶点,直到走到尽头时回溯,再继续走到底 + 回溯,以此类推……直至所有顶点遍历完成时结束。 diff --git a/chapter_hashing/hash_collision.md b/chapter_hashing/hash_collision.md index 269254c6c..005e57a19 100644 --- a/chapter_hashing/hash_collision.md +++ b/chapter_hashing/hash_collision.md @@ -2,7 +2,7 @@ comments: true --- -# 6.2. 哈希冲突 +# 6.2. 哈希冲突 理想情况下,哈希函数应该为每个输入产生唯一的输出,使得 key 和 value 一一对应。而实际上,往往存在向哈希函数输入不同的 key 而产生相同输出的情况,这种情况被称为「哈希冲突 Hash Collision」。哈希冲突会导致查询结果错误,从而严重影响哈希表的可用性。 @@ -12,7 +12,7 @@ comments: true 另一方面,**考虑通过优化数据结构以缓解哈希冲突**,常见的方法有「链式地址」和「开放寻址」。 -## 6.2.1. 哈希表扩容 +## 6.2.1. 哈希表扩容 「负载因子 Load Factor」定义为 **哈希表中元素数量除以桶槽数量(即数组大小)**,代表哈希冲突的严重程度。 @@ -20,7 +20,7 @@ comments: true 与数组扩容类似,**哈希表扩容操作的开销很大**,因为需要将所有键值对从原哈希表依次移动至新哈希表。 -## 6.2.2. 链式地址 +## 6.2.2. 链式地址 在原始哈希表中,桶内的每个地址只能存储一个元素(即键值对)。**考虑将单个元素转化成一个链表,将所有冲突元素都存储在一个链表中**。 @@ -39,7 +39,7 @@ comments: true 为了缓解时间效率问题,**可以把「链表」转化为「AVL 树」或「红黑树」**,将查询操作的时间复杂度优化至 $O(\log n)$ 。 -## 6.2.3. 开放寻址 +## 6.2.3. 开放寻址 「开放寻址」不引入额外数据结构,而是通过“多次探测”来解决哈希冲突。根据探测方法的不同,主要分为 **线性探测、平方探测、多次哈希**。 diff --git a/chapter_hashing/hash_map.md b/chapter_hashing/hash_map.md index 86c68ff43..a5b0ee59b 100755 --- a/chapter_hashing/hash_map.md +++ b/chapter_hashing/hash_map.md @@ -2,7 +2,7 @@ comments: true --- -# 6.1. 哈希表 +# 6.1. 哈希表 哈希表通过建立「键 key」和「值 value」之间的映射,实现高效的元素查找。具体地,输入一个 key ,在哈希表中查询并获取 value ,时间复杂度为 $O(1)$ 。 @@ -12,7 +12,7 @@ comments: trueFig. 哈希表抽象表示
-## 6.1.1. 哈希表效率 +## 6.1.1. 哈希表效率 除了哈希表之外,还可以使用以下数据结构来实现上述查询功能: @@ -33,7 +33,7 @@ comments: true -## 6.1.2. 哈希表常用操作 +## 6.1.2. 哈希表常用操作 哈希表的基本操作包括 **初始化、查询操作、添加与删除键值对**。 @@ -391,7 +391,7 @@ comments: true ``` -## 6.1.3. 哈希函数 +## 6.1.3. 哈希函数 哈希表中存储元素的数据结构被称为「桶 Bucket」,底层实现可能是数组、链表、二叉树(红黑树),或是它们的组合。 @@ -1265,7 +1265,7 @@ $$ } ``` -## 6.1.4. 哈希冲突 +## 6.1.4. 哈希冲突 细心的同学可能会发现,**哈希函数 $f(x) = x \% 100$ 会在某些情况下失效**。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 $12836$ 和 $20336$ ,则有 diff --git a/chapter_hashing/summary.md b/chapter_hashing/summary.md index a66b041a3..eacdfcfe7 100644 --- a/chapter_hashing/summary.md +++ b/chapter_hashing/summary.md @@ -2,4 +2,4 @@ comments: true --- -# 6.3. 小结 +# 6.3. 小结 diff --git a/chapter_heap/heap.md b/chapter_heap/heap.md index e9aec8f73..7aee9075f 100644 --- a/chapter_heap/heap.md +++ b/chapter_heap/heap.md @@ -2,7 +2,7 @@ comments: true --- -# 8.1. 堆 +# 8.1. 堆 「堆 Heap」是一棵限定条件下的「完全二叉树」。根据成立条件,堆主要分为两种类型: @@ -11,13 +11,13 @@ comments: true ![min_heap_and_max_heap](heap.assets/min_heap_and_max_heap.png) -## 8.1.1. 堆术语与性质 +## 8.1.1. 堆术语与性质 - 由于堆是完全二叉树,因此最底层结点靠左填充,其它层结点皆被填满。 - 二叉树中的根结点对应「堆顶」,底层最靠右结点对应「堆底」。 - 对于大顶堆 / 小顶堆,其堆顶元素(即根结点)的值最大 / 最小。 -## 8.1.2. 堆常用操作 +## 8.1.2. 堆常用操作 值得说明的是,多数编程语言提供的是「优先队列 Priority Queue」,其是一种抽象数据结构,**定义为具有出队优先级的队列**。 @@ -306,7 +306,7 @@ comments: true ``` -## 8.1.3. 堆的实现 +## 8.1.3. 堆的实现 下文实现的是「大顶堆」,若想转换为「小顶堆」,将所有大小逻辑判断取逆(例如将 $\geq$ 替换为 $\leq$ )即可,有兴趣的同学可自行实现。 @@ -1456,7 +1456,7 @@ $$ 进一步地,高度为 $h$ 的完美二叉树的结点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。 -## 8.1.4. 堆常见应用 +## 8.1.4. 堆常见应用 - **优先队列**。堆常作为实现优先队列的首选数据结构,入队和出队操作时间复杂度为 $O(\log n)$ ,建队操作为 $O(n)$ ,皆非常高效。 - **堆排序**。给定一组数据,我们使用其建堆,并依次全部弹出,则可以得到有序的序列。当然,堆排序一般无需弹出元素,仅需每轮将堆顶元素交换至数组尾部并减小堆的长度即可。 diff --git a/chapter_introduction/algorithms_are_everywhere.md b/chapter_introduction/algorithms_are_everywhere.md index b0631ab95..c89a9f54c 100644 --- a/chapter_introduction/algorithms_are_everywhere.md +++ b/chapter_introduction/algorithms_are_everywhere.md @@ -2,7 +2,7 @@ comments: true --- -# 1.1. 算法无处不在 +# 1.1. 算法无处不在 听到“算法”这个词,我们一般会联想到数学。但实际上,大多数算法并不包含复杂的数学,而更像是在考察基本逻辑,而这些逻辑在我们日常生活中处处可见。 diff --git a/chapter_introduction/what_is_dsa.md b/chapter_introduction/what_is_dsa.md index d104e4852..3a936f942 100644 --- a/chapter_introduction/what_is_dsa.md +++ b/chapter_introduction/what_is_dsa.md @@ -2,9 +2,9 @@ comments: true --- -# 1.2. 算法是什么 +# 1.2. 算法是什么 -## 1.2.1. 算法定义 +## 1.2.1. 算法定义 「算法 Algorithm」是在有限时间内解决特定问题的一组指令或操作步骤。算法具有以下特性: @@ -13,7 +13,7 @@ comments: true - 具有可行性,可在有限步骤、有限时间、有限内存空间下完成。 - 独立于编程语言,即可用多种语言实现。 -## 1.2.2. 数据结构定义 +## 1.2.2. 数据结构定义 「数据结构 Data Structure」是在计算机中组织与存储数据的方式。为了提高数据存储和操作性能,数据结构的设计原则有: @@ -23,7 +23,7 @@ comments: true 数据结构的设计是一个充满权衡的过程,这意味着如果获得某方面的优势,则往往需要在另一方面做出妥协。例如,链表相对于数组,数据添加删除操作更加方便,但牺牲了数据的访问速度;图相对于链表,提供了更多的逻辑信息,但需要占用更多的内存空间。 -## 1.2.3. 数据结构与算法的关系 +## 1.2.3. 数据结构与算法的关系 「数据结构」与「算法」是高度相关、紧密嵌合的,体现在: diff --git a/chapter_preface/about_the_book.md b/chapter_preface/about_the_book.md index 67a26b465..e9936f325 100644 --- a/chapter_preface/about_the_book.md +++ b/chapter_preface/about_the_book.md @@ -2,7 +2,7 @@ comments: true --- -# 0.1. 关于本书 +# 0.1. 关于本书 五年前发生的一件事,成为了我职业生涯的重要转折点。当时的我在交大读研,对互联网求职一无所知,但仍然硬着头皮申请了 Microsoft 软件工程师实习。面试官让我在白板上写出“快速排序”代码,我畏畏缩缩地写了一个“冒泡排序”,并且还写错了` (ToT) ` 。从面试官的表情上,我看到了一个大大的 "GG" 。 @@ -12,7 +12,7 @@ comments: trueFig. 冒泡操作
-## 11.2.1. 算法流程 +## 11.2.1. 算法流程 1. 设数组长度为 $n$ ,完成第一轮「冒泡」后,数组最大元素已在正确位置,接下来只需排序剩余 $n - 1$ 个元素。 2. 同理,对剩余 $n - 1$ 个元素执行「冒泡」,可将第二大元素交换至正确位置,因而待排序元素只剩 $n - 2$ 个。 @@ -231,7 +231,7 @@ comments: true } ``` -## 11.2.2. 算法特性 +## 11.2.2. 算法特性 **时间复杂度 $O(n^2)$** :各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 @@ -243,7 +243,7 @@ comments: true **自适应排序**:引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。 -## 11.2.3. 效率优化 +## 11.2.3. 效率优化 我们发现,若在某轮「冒泡」中未执行任何交换操作,则说明数组已经完成排序,可直接返回结果。考虑可以增加一个标志位 `flag` 来监听该情况,若出现则直接返回。 diff --git a/chapter_sorting/insertion_sort.md b/chapter_sorting/insertion_sort.md index 21e8f8279..feb8c304c 100755 --- a/chapter_sorting/insertion_sort.md +++ b/chapter_sorting/insertion_sort.md @@ -2,7 +2,7 @@ comments: true --- -# 11.3. 插入排序 +# 11.3. 插入排序 「插入排序 Insertion Sort」是一种基于 **数组插入操作** 的排序算法。 @@ -14,7 +14,7 @@ comments: trueFig. 插入操作
-## 11.3.1. 算法流程 +## 11.3.1. 算法流程 1. 第 1 轮先选取数组的 **第 2 个元素** 为 `base` ,执行「插入操作」后,**数组前 2 个元素已完成排序**。 2. 第 2 轮选取 **第 3 个元素** 为 `base` ,执行「插入操作」后,**数组前 3 个元素已完成排序**。 @@ -197,7 +197,7 @@ comments: true } ``` -## 11.3.2. 算法特性 +## 11.3.2. 算法特性 **时间复杂度 $O(n^2)$** :最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 @@ -209,7 +209,7 @@ comments: true **自适应排序**:最佳情况下,时间复杂度为 $O(n)$ 。 -## 11.3.3. 插入排序 vs 冒泡排序 +## 11.3.3. 插入排序 vs 冒泡排序 !!! question diff --git a/chapter_sorting/intro_to_sort.md b/chapter_sorting/intro_to_sort.md index 9fe7a3ee3..9b46001de 100644 --- a/chapter_sorting/intro_to_sort.md +++ b/chapter_sorting/intro_to_sort.md @@ -2,7 +2,7 @@ comments: true --- -# 11.1. 排序简介 +# 11.1. 排序简介 「排序算法 Sorting Algorithm」使得列表中的所有元素按照从小到大的顺序排列。 @@ -13,7 +13,7 @@ comments: trueFig. 排序中的不同元素类型和判断规则
-## 11.1.1. 评价维度 +## 11.1.1. 评价维度 排序算法主要可根据 **稳定性 、就地性 、自适应性 、比较类** 来分类。 @@ -64,7 +64,7 @@ comments: true 「比较类排序」的时间复杂度最优为 $O(n \log n)$ ;而「非比较类排序」可以达到 $O(n)$ 的时间复杂度,但通用性较差。 -## 11.1.2. 理想排序算法 +## 11.1.2. 理想排序算法 - **运行快**,即时间复杂度低; - **稳定排序**,即排序后相等元素的相对位置不变化; diff --git a/chapter_sorting/merge_sort.md b/chapter_sorting/merge_sort.md index 5e530f2e2..3e7efadac 100755 --- a/chapter_sorting/merge_sort.md +++ b/chapter_sorting/merge_sort.md @@ -2,7 +2,7 @@ comments: true --- -# 11.5. 归并排序 +# 11.5. 归并排序 「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并」两个阶段: @@ -13,7 +13,7 @@ comments: trueFig. 归并排序两阶段:划分与合并
-## 11.5.1. 算法流程 +## 11.5.1. 算法流程 **「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组**,直至长度为 1 ; @@ -447,7 +447,7 @@ comments: true - `nums` 的待合并区间为 `[left, right]` ,而因为 `tmp` 只复制了 `nums` 该区间元素,所以 `tmp` 对应区间为 `[0, right - left]` ,**需要特别注意代码中各个变量的含义**。 - 判断 `tmp[i]` 和 `tmp[j]` 的大小的操作中,还 **需考虑当子数组遍历完成后的索引越界问题**,即 `i > leftEnd` 和 `j > rightEnd` 的情况,索引越界的优先级是最高的,例如如果左子数组已经被合并完了,那么不用继续判断,直接合并右子数组元素即可。 -## 11.5.2. 算法特性 +## 11.5.2. 算法特性 - **时间复杂度 $O(n \log n)$** :划分形成高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,总体使用 $O(n \log n)$ 时间。 - **空间复杂度 $O(n)$** :需借助辅助数组实现合并,使用 $O(n)$ 大小的额外空间;递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。 @@ -455,7 +455,7 @@ comments: true - **稳定排序**:在合并时可保证相等元素的相对位置不变。 - **非自适应排序**:对于任意输入数据,归并排序的时间复杂度皆相同。 -## 11.5.3. 链表排序 * +## 11.5.3. 链表排序 * 归并排序有一个很特别的优势,用于排序链表时有很好的性能表现,**空间复杂度可被优化至 $O(1)$** ,这是因为: diff --git a/chapter_sorting/quick_sort.md b/chapter_sorting/quick_sort.md index 200bc93e2..69e4a2022 100755 --- a/chapter_sorting/quick_sort.md +++ b/chapter_sorting/quick_sort.md @@ -2,7 +2,7 @@ comments: true --- -# 11.4. 快速排序 +# 11.4. 快速排序 「快速排序 Quick Sort」是一种基于“分治思想”的排序算法,速度很快、应用很广。 @@ -288,7 +288,7 @@ comments: true 哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题**。 -## 11.4.1. 算法流程 +## 11.4.1. 算法流程 1. 首先,对数组执行一次「哨兵划分」,得到待排序的 **左子数组** 和 **右子数组**; 2. 接下来,对 **左子数组** 和 **右子数组** 分别 **递归执行**「哨兵划分」…… @@ -451,7 +451,7 @@ comments: true } ``` -## 11.4.2. 算法特性 +## 11.4.2. 算法特性 **平均时间复杂度 $O(n \log n)$** :平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 @@ -465,7 +465,7 @@ comments: true **自适应排序**:最差情况下,时间复杂度劣化至 $O(n^2)$ 。 -## 11.4.3. 快排为什么快? +## 11.4.3. 快排为什么快? 从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 **效率更高**,这是因为: @@ -473,7 +473,7 @@ comments: true - **缓存使用效率高**:哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。 - **复杂度的常数系数低**:在提及的三种算法中,快速排序的 **比较**、**赋值**、**交换** 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。 -## 11.4.4. 基准数优化 +## 11.4.4. 基准数优化 **普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 @@ -796,7 +796,7 @@ comments: true } ``` -## 11.4.5. 尾递归优化 +## 11.4.5. 尾递归优化 **普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 diff --git a/chapter_sorting/summary.md b/chapter_sorting/summary.md index 71cc82609..ad67bda93 100644 --- a/chapter_sorting/summary.md +++ b/chapter_sorting/summary.md @@ -2,5 +2,5 @@ comments: true --- -# 11.6. 小结 +# 11.6. 小结 diff --git a/chapter_stack_and_queue/deque.md b/chapter_stack_and_queue/deque.md index b9f9b4c79..7e2386b69 100644 --- a/chapter_stack_and_queue/deque.md +++ b/chapter_stack_and_queue/deque.md @@ -2,7 +2,7 @@ comments: true --- -# 5.3. 双向队列 +# 5.3. 双向队列 对于队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。 @@ -10,7 +10,7 @@ comments: trueFig. 双向队列的操作
-## 5.3.1. 双向队列常用操作 +## 5.3.1. 双向队列常用操作 双向队列的常用操作见下表,方法名需根据特定语言来确定。 @@ -293,7 +293,7 @@ comments: true ``` -## 5.3.2. 双向队列实现 * +## 5.3.2. 双向队列实现 * 与队列类似,双向队列同样可以使用链表或数组来实现。 diff --git a/chapter_stack_and_queue/queue.md b/chapter_stack_and_queue/queue.md index 27d308beb..9616cfd83 100755 --- a/chapter_stack_and_queue/queue.md +++ b/chapter_stack_and_queue/queue.md @@ -2,7 +2,7 @@ comments: true --- -# 5.2. 队列 +# 5.2. 队列 「队列 Queue」是一种遵循「先入先出 first in, first out」数据操作规则的线性数据结构。顾名思义,队列模拟的是排队现象,即外面的人不断加入队列尾部,而处于队列头部的人不断地离开。 @@ -12,7 +12,7 @@ comments: trueFig. 队列的先入先出特性
-## 5.2.1. 队列常用操作 +## 5.2.1. 队列常用操作 队列的常用操作见下表,方法名需根据特定语言来确定。 @@ -262,7 +262,7 @@ comments: true ``` -## 5.2.2. 队列实现 +## 5.2.2. 队列实现 队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。 @@ -1628,11 +1628,11 @@ comments: true 以上实现的队列仍存在局限性,即长度不可变。不过这个问题很容易解决,我们可以将数组替换为列表(即动态数组),从而引入扩容机制。有兴趣的同学可以尝试自行实现。 -## 5.2.3. 两种实现对比 +## 5.2.3. 两种实现对比 与栈的结论一致,在此不再赘述。 -## 5.2.4. 队列典型应用 +## 5.2.4. 队列典型应用 - **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 - **各种待办事项**。任何需要实现“先来后到”的功能,例如打印机的任务队列、餐厅的出餐队列等等。 diff --git a/chapter_stack_and_queue/stack.md b/chapter_stack_and_queue/stack.md index 77ea6c8cf..4c7858f1a 100755 --- a/chapter_stack_and_queue/stack.md +++ b/chapter_stack_and_queue/stack.md @@ -2,7 +2,7 @@ comments: true --- -# 5.1. 栈 +# 5.1. 栈 「栈 Stack」是一种遵循「先入后出 first in, last out」数据操作规则的线性数据结构。我们可以将栈类比为放在桌面上的一摞盘子,如果需要拿出底部的盘子,则需要先将上面的盘子依次取出。 @@ -14,7 +14,7 @@ comments: trueFig. 栈的先入后出特性
-## 5.1.1. 栈常用操作 +## 5.1.1. 栈常用操作 栈的常用操作见下表(方法命名以 Java 为例)。 @@ -261,7 +261,7 @@ comments: true ``` -## 5.1.2. 栈的实现 +## 5.1.2. 栈的实现 为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。 @@ -1305,7 +1305,7 @@ comments: true } ``` -## 5.1.3. 两种实现对比 +## 5.1.3. 两种实现对比 ### 支持操作 @@ -1330,7 +1330,7 @@ comments: true 综上,我们不能简单地确定哪种实现更加省内存,需要 case-by-case 地分析。 -## 5.1.4. 栈典型应用 +## 5.1.4. 栈典型应用 - **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就将上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 - **程序内存管理**。每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。 diff --git a/chapter_stack_and_queue/summary.md b/chapter_stack_and_queue/summary.md index 19cf6c8c3..3e1fe19aa 100644 --- a/chapter_stack_and_queue/summary.md +++ b/chapter_stack_and_queue/summary.md @@ -2,7 +2,7 @@ comments: true --- -# 5.4. 小结 +# 5.4. 小结 - 栈是一种遵循先入后出的数据结构,可以使用数组或链表实现。 - 在时间效率方面,栈的数组实现具有更好的平均效率,但扩容时会导致单次入栈操作的时间复杂度劣化至 $O(n)$ 。相对地,栈的链表实现具有更加稳定的效率表现。 diff --git a/chapter_tree/avl_tree.md b/chapter_tree/avl_tree.md index 7514584a3..232b0f819 100644 --- a/chapter_tree/avl_tree.md +++ b/chapter_tree/avl_tree.md @@ -2,7 +2,7 @@ comments: true --- -# 7.4. AVL 树 * +# 7.4. AVL 树 * 在「二叉搜索树」章节中提到,在进行多次插入与删除操作后,二叉搜索树可能会退化为链表。此时所有操作的时间复杂度都会由 $O(\log n)$ 劣化至 $O(n)$ 。 @@ -18,7 +18,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit 换言之,在频繁增删查改的使用场景中,AVL 树可始终保持很高的数据增删查改效率,具有很好的应用价值。 -## 7.4.1. AVL 树常见术语 +## 7.4.1. AVL 树常见术语 「AVL 树」既是「二叉搜索树」又是「平衡二叉树」,同时满足这两种二叉树的所有性质,因此又被称为「平衡二叉搜索树」。 @@ -444,7 +444,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit 设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。 -## 7.4.2. AVL 树旋转 +## 7.4.2. AVL 树旋转 AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡**。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 @@ -1170,7 +1170,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 } ``` -## 7.4.3. AVL 树常用操作 +## 7.4.3. AVL 树常用操作 ### 插入结点 @@ -1944,7 +1944,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 「AVL 树」的结点查找操作与「二叉搜索树」一致,在此不再赘述。 -## 7.4.4. AVL 树典型应用 +## 7.4.4. AVL 树典型应用 - 组织存储大型数据,适用于高频查找、低频增删场景; - 用于建立数据库中的索引系统; diff --git a/chapter_tree/binary_search_tree.md b/chapter_tree/binary_search_tree.md index 1c89035d8..40487f351 100755 --- a/chapter_tree/binary_search_tree.md +++ b/chapter_tree/binary_search_tree.md @@ -2,7 +2,7 @@ comments: true --- -# 7.3. 二叉搜索树 +# 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. 二叉搜索树的操作 +## 7.3.1. 二叉搜索树的操作 ### 查找结点 @@ -1137,7 +1137,7 @@ comments: true ![bst_inorder_traversal](binary_search_tree.assets/bst_inorder_traversal.png) -## 7.3.2. 二叉搜索树的效率 +## 7.3.2. 二叉搜索树的效率 假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为: @@ -1166,7 +1166,7 @@ comments: true -## 7.3.3. 二叉搜索树的退化 +## 7.3.3. 二叉搜索树的退化 理想情况下,我们希望二叉搜索树的是“左右平衡”的(详见「平衡二叉树」章节),此时可以在 $\log n$ 轮循环内查找任意结点。 @@ -1178,7 +1178,7 @@ comments: true ![bst_degradation](binary_search_tree.assets/bst_degradation.png) -## 7.3.4. 二叉搜索树常见应用 +## 7.3.4. 二叉搜索树常见应用 - 系统中的多级索引,高效查找、插入、删除操作。 - 各种搜索算法的底层数据结构。 diff --git a/chapter_tree/binary_tree.md b/chapter_tree/binary_tree.md index a6a71577a..932a9e59e 100644 --- a/chapter_tree/binary_tree.md +++ b/chapter_tree/binary_tree.md @@ -2,7 +2,7 @@ comments: true --- -# 7.1. 二叉树 +# 7.1. 二叉树 「二叉树 Binary Tree」是一种非线性数据结构,代表着祖先与后代之间的派生关系,体现着“一分为二”的分治逻辑。类似于链表,二叉树也是以结点为单位存储的,结点包含「值」和两个「指针」。 @@ -135,7 +135,7 @@ comments: trueFig. 子结点与子树
-## 7.1.1. 二叉树常见术语 +## 7.1.1. 二叉树常见术语 二叉树的术语较多,建议尽量理解并记住。后续可能遗忘,可以在需要使用时回来查看确认。 @@ -156,7 +156,7 @@ comments: true 值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。 -## 7.1.2. 二叉树基本操作 +## 7.1.2. 二叉树基本操作 **初始化二叉树**。与链表类似,先初始化结点,再构建引用指向(即指针)。 @@ -422,7 +422,7 @@ comments: true 插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 -## 7.1.3. 常见二叉树类型 +## 7.1.3. 常见二叉树类型 ### 完美二叉树 @@ -454,7 +454,7 @@ comments: true ![balanced_binary_tree](binary_tree.assets/balanced_binary_tree.png) -## 7.1.4. 二叉树的退化 +## 7.1.4. 二叉树的退化 当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。 @@ -478,7 +478,7 @@ comments: true -## 7.1.5. 二叉树表示方式 * +## 7.1.5. 二叉树表示方式 * 我们一般使用二叉树的「链表表示」,即存储单位为结点 `TreeNode` ,结点之间通过指针(引用)相连接。本文前述示例代码展示了二叉树在链表表示下的各项基本操作。 diff --git a/chapter_tree/binary_tree_traversal.md b/chapter_tree/binary_tree_traversal.md index 9f02f6918..d4624ddf2 100755 --- a/chapter_tree/binary_tree_traversal.md +++ b/chapter_tree/binary_tree_traversal.md @@ -2,13 +2,13 @@ comments: true --- -# 7.2. 二叉树遍历 +# 7.2. 二叉树遍历 从物理结构角度看,树是一种基于链表的数据结构,因此遍历方式也是通过指针(即引用)逐个遍历结点。同时,树还是一种非线性数据结构,这导致遍历树比遍历链表更加复杂,需要使用搜索算法来实现。 常见的二叉树遍历方式有层序遍历、前序遍历、中序遍历、后序遍历。 -## 7.2.1. 层序遍历 +## 7.2.1. 层序遍历 「层序遍历 Level-Order Traversal」从顶至底、一层一层地遍历二叉树,并在每层中按照从左到右的顺序访问结点。 @@ -250,7 +250,7 @@ comments: true **空间复杂度**:当为满二叉树时达到最差情况,遍历到最底层前,队列中最多同时存在 $\frac{n + 1}{2}$ 个结点,使用 $O(n)$ 空间。 -## 7.2.2. 前序、中序、后序遍历 +## 7.2.2. 前序、中序、后序遍历 相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。 diff --git a/chapter_tree/summary.md b/chapter_tree/summary.md index 8566eaa8e..092f183ca 100644 --- a/chapter_tree/summary.md +++ b/chapter_tree/summary.md @@ -2,7 +2,7 @@ comments: true --- -# 7.5. 小结 +# 7.5. 小结 - 二叉树是一种非线性数据结构,代表着“一分为二”的分治逻辑。二叉树的结点包含「值」和两个「指针」,分别指向左子结点和右子结点。 - 选定二叉树中某结点,将其左(右)子结点以下形成的树称为左(右)子树。