欢迎在评论区留下你的见解、问题或建议
- - -diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 051145069..0aa056c2b 100755 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -4,7 +4,7 @@ comments: true # 4.1 数组 -「数组 array」是一种线性数据结构,其将相同类型的元素存储在连续的内存空间中。我们将元素在数组中的位置称为该元素的「索引 index」。图 4-1 展示了数组的主要概念和存储方式。 +数组(array)是一种线性数据结构,其将相同类型的元素存储在连续的内存空间中。我们将元素在数组中的位置称为该元素的索引(index)。图 4-1 展示了数组的主要概念和存储方式。 ![数组定义与存储方式](array.assets/array_definition.png){ class="animation-figure" } diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index 75079a6e3..e53bb5988 100755 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -6,7 +6,7 @@ comments: true 内存空间是所有程序的公共资源,在一个复杂的系统运行环境下,空闲的内存空间可能散落在内存各处。我们知道,存储数组的内存空间必须是连续的,而当数组非常大时,内存可能无法提供如此大的连续空间。此时链表的灵活性优势就体现出来了。 -「链表 linked list」是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接。引用记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点。 +链表(linked list)是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接。引用记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点。 链表的设计使得各个节点可以分散存储在内存各处,它们的内存地址无须连续。 @@ -14,7 +14,7 @@ comments: true
图 4-5 链表定义与存储方式
-观察图 4-5 ,链表的组成单位是「节点 node」对象。每个节点都包含两项数据:节点的“值”和指向下一节点的“引用”。 +观察图 4-5 ,链表的组成单位是节点(node)对象。每个节点都包含两项数据:节点的“值”和指向下一节点的“引用”。 - 链表的首个节点被称为“头节点”,最后一个节点被称为“尾节点”。 - 尾节点指向的是“空”,它在 Java、C++ 和 Python 中分别被记为 `null`、`nullptr` 和 `None` 。 diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index 1ec2ddb22..9612c17be 100755 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -4,14 +4,14 @@ comments: true # 4.3 列表 -「列表 list」是一个抽象的数据结构概念,它表示元素的有序集合,支持元素访问、修改、添加、删除和遍历等操作,无须使用者考虑容量限制的问题。列表可以基于链表或数组实现。 +列表(list)是一个抽象的数据结构概念,它表示元素的有序集合,支持元素访问、修改、添加、删除和遍历等操作,无须使用者考虑容量限制的问题。列表可以基于链表或数组实现。 - 链表天然可以看作一个列表,其支持元素增删查改操作,并且可以灵活动态扩容。 - 数组也支持元素增删查改,但由于其长度不可变,因此只能看作一个具有长度限制的列表。 当使用数组实现列表时,**长度不可变的性质会导致列表的实用性降低**。这是因为我们通常无法事先确定需要存储多少数据,从而难以选择合适的列表长度。若长度过小,则很可能无法满足使用需求;若长度过大,则会造成内存空间浪费。 -为解决此问题,我们可以使用「动态数组 dynamic array」来实现列表。它继承了数组的各项优点,并且可以在程序运行过程中进行动态扩容。 +为解决此问题,我们可以使用动态数组(dynamic array)来实现列表。它继承了数组的各项优点,并且可以在程序运行过程中进行动态扩容。 实际上,**许多编程语言中的标准库提供的列表是基于动态数组实现的**,例如 Python 中的 `list` 、Java 中的 `ArrayList` 、C++ 中的 `vector` 和 C# 中的 `List` 等。在接下来的讨论中,我们将把“列表”和“动态数组”视为等同的概念。 diff --git a/docs/chapter_array_and_linkedlist/ram_and_cache.md b/docs/chapter_array_and_linkedlist/ram_and_cache.md index c5ad8930d..aa2c234e9 100644 --- a/docs/chapter_array_and_linkedlist/ram_and_cache.md +++ b/docs/chapter_array_and_linkedlist/ram_and_cache.md @@ -11,7 +11,7 @@ status: new ## 4.4.1 计算机存储设备 -计算机中包括三种类型的存储设备:「硬盘 hard disk」、「内存 random-access memory, RAM」、「缓存 cache memory」。表 4-2 展示了它们在计算机系统中的不同角色和性能特点。 +计算机中包括三种类型的存储设备:硬盘(hard disk)、内存(random-access memory, RAM)、缓存(cache memory)。表 4-2 展示了它们在计算机系统中的不同角色和性能特点。表 4-2 计算机的存储设备
@@ -58,9 +58,9 @@ status: new ## 4.4.3 数据结构的缓存效率 -缓存虽然在空间容量上远小于内存,但它比内存快得多,在程序执行速度上起着至关重要的作用。由于缓存的容量有限,只能存储一小部分频繁访问的数据,因此当 CPU 尝试访问的数据不在缓存中时,就会发生「缓存未命中 cache miss」,此时 CPU 不得不从速度较慢的内存中加载所需数据。 +缓存虽然在空间容量上远小于内存,但它比内存快得多,在程序执行速度上起着至关重要的作用。由于缓存的容量有限,只能存储一小部分频繁访问的数据,因此当 CPU 尝试访问的数据不在缓存中时,就会发生缓存未命中(cache miss),此时 CPU 不得不从速度较慢的内存中加载所需数据。 -显然,**“缓存未命中”越少,CPU 读写数据的效率就越高**,程序性能也就越好。我们将 CPU 从缓存中成功获取数据的比例称为「缓存命中率 cache hit rate」,这个指标通常用来衡量缓存效率。 +显然,**“缓存未命中”越少,CPU 读写数据的效率就越高**,程序性能也就越好。我们将 CPU 从缓存中成功获取数据的比例称为缓存命中率(cache hit rate),这个指标通常用来衡量缓存效率。 为了尽可能达到更高的效率,缓存会采取以下数据加载机制。 diff --git a/docs/chapter_backtracking/backtracking_algorithm.md b/docs/chapter_backtracking/backtracking_algorithm.md index fad8431ca..f2eabbe27 100644 --- a/docs/chapter_backtracking/backtracking_algorithm.md +++ b/docs/chapter_backtracking/backtracking_algorithm.md @@ -4,7 +4,7 @@ comments: true # 13.1 回溯算法 -「回溯算法 backtracking algorithm」是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。 +回溯算法(backtracking algorithm)是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。 回溯算法通常采用“深度优先搜索”来遍历解空间。在“二叉树”章节中,我们提到前序、中序和后序遍历都属于深度优先搜索。接下来,我们利用前序遍历构造一个回溯问题,逐步了解回溯算法的工作原理。 diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.md b/docs/chapter_computational_complexity/iteration_and_recursion.md index b60974327..5fe60d7a1 100644 --- a/docs/chapter_computational_complexity/iteration_and_recursion.md +++ b/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -8,7 +8,7 @@ comments: true ## 2.2.1 迭代 -「迭代 iteration」是一种重复执行某个任务的控制结构。在迭代中,程序会在满足一定的条件下重复执行某段代码,直到这个条件不再满足。 +迭代(iteration)是一种重复执行某个任务的控制结构。在迭代中,程序会在满足一定的条件下重复执行某段代码,直到这个条件不再满足。 ### 1. for 循环 @@ -944,7 +944,7 @@ comments: true ## 2.2.2 递归 - 「递归 recursion」是一种算法策略,通过函数调用自身来解决问题。它主要包含两个阶段。 + 递归(recursion)是一种算法策略,通过函数调用自身来解决问题。它主要包含两个阶段。 1. **递**:程序不断深入地调用自身,通常传入更小或更简化的参数,直到达到“终止条件”。 2. **归**:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的结果。 @@ -1196,7 +1196,7 @@ comments: true ### 2. 尾递归 -有趣的是,**如果函数在返回前的最后一步才进行递归调用**,则该函数可以被编译器或解释器优化,使其在空间效率上与迭代相当。这种情况被称为「尾递归 tail recursion」。 +有趣的是,**如果函数在返回前的最后一步才进行递归调用**,则该函数可以被编译器或解释器优化,使其在空间效率上与迭代相当。这种情况被称为尾递归(tail recursion)。 - **普通递归**:当函数返回到上一层级的函数后,需要继续执行代码,因此系统需要保存上一层调用的上下文。 - **尾递归**:递归调用是函数返回前的最后一个操作,这意味着函数返回到上一层级后,无须继续执行其他操作,因此系统无须保存上一层函数的上下文。 @@ -1618,7 +1618,7 @@ comments: true -观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如图 2-6 所示,这样不断递归调用下去,最终将产生一棵层数为 $n$ 的「递归树 recursion tree」。 +观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如图 2-6 所示,这样不断递归调用下去,最终将产生一棵层数为 $n$ 的递归树(recursion tree)。 ![斐波那契数列的递归树](iteration_and_recursion.assets/recursion_tree.png){ class="animation-figure" } diff --git a/docs/chapter_computational_complexity/performance_evaluation.md b/docs/chapter_computational_complexity/performance_evaluation.md index 35373a9c2..0ac5b8a6c 100644 --- a/docs/chapter_computational_complexity/performance_evaluation.md +++ b/docs/chapter_computational_complexity/performance_evaluation.md @@ -28,11 +28,11 @@ comments: true ## 2.1.2 理论估算 -由于实际测试具有较大的局限性,因此我们可以考虑仅通过一些计算来评估算法的效率。这种估算方法被称为「渐近复杂度分析 asymptotic complexity analysis」,简称「复杂度分析」。 +由于实际测试具有较大的局限性,因此我们可以考虑仅通过一些计算来评估算法的效率。这种估算方法被称为渐近复杂度分析(asymptotic complexity analysis),简称复杂度分析。 复杂度分析能够体现算法运行所需的时间和空间资源与输入数据大小之间的关系。**它描述了随着输入数据大小的增加,算法执行所需时间和空间的增长趋势**。这个定义有些拗口,我们可以将其分为三个重点来理解。 -- “时间和空间资源”分别对应「时间复杂度 time complexity」和「空间复杂度 space complexity」。 +- “时间和空间资源”分别对应时间复杂度(time complexity)和空间复杂度(space complexity)。 - “随着输入数据大小的增加”意味着复杂度反映了算法运行效率与输入数据体量之间的关系。 - “时间和空间的增长趋势”表示复杂度分析关注的不是运行时间或占用空间的具体值,而是时间或空间增长的“快慢”。 diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index a01ad4247..8e7da085b 100755 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -4,7 +4,7 @@ comments: true # 2.4 空间复杂度 -「空间复杂度 space complexity」用于衡量算法占用内存空间随着数据量变大时的增长趋势。这个概念与时间复杂度非常类似,只需将“运行时间”替换为“占用内存空间”。 +空间复杂度(space complexity)用于衡量算法占用内存空间随着数据量变大时的增长趋势。这个概念与时间复杂度非常类似,只需将“运行时间”替换为“占用内存空间”。 ## 2.4.1 算法相关空间 diff --git a/docs/chapter_computational_complexity/summary.md b/docs/chapter_computational_complexity/summary.md index 7b2e6ace8..341a79cd4 100644 --- a/docs/chapter_computational_complexity/summary.md +++ b/docs/chapter_computational_complexity/summary.md @@ -36,7 +36,7 @@ comments: true **Q**:函数和方法这两个术语的区别是什么? -「函数 function」可以被独立执行,所有参数都以显式传递。「方法 method」与一个对象关联,被隐式传递给调用它的对象,能够对类的实例中包含的数据进行操作。 +函数(function)可以被独立执行,所有参数都以显式传递。方法(method)与一个对象关联,被隐式传递给调用它的对象,能够对类的实例中包含的数据进行操作。 下面以几种常见的编程语言为例来说明。 diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 8053318bc..ff56b9cf0 100755 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -719,7 +719,7 @@ $$ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因此它的时间复杂度是线性阶。 -我们将线性阶的时间复杂度记为 $O(n)$ ,这个数学符号称为「大 $O$ 记号 big-$O$ notation」,表示函数 $T(n)$ 的「渐近上界 asymptotic upper bound」。 +我们将线性阶的时间复杂度记为 $O(n)$ ,这个数学符号称为大($O$ 记号 big-$O$ notation),表示函数 $T(n)$ 的渐近上界(asymptotic upper bound)。 时间复杂度分析本质上是计算“操作数量 $T(n)$”的渐近上界,它具有明确的数学定义。 diff --git a/docs/chapter_data_structure/character_encoding.md b/docs/chapter_data_structure/character_encoding.md index 952df435d..4ddb43156 100644 --- a/docs/chapter_data_structure/character_encoding.md +++ b/docs/chapter_data_structure/character_encoding.md @@ -8,21 +8,21 @@ comments: true ## 3.4.1 ASCII 字符集 -「ASCII 码」是最早出现的字符集,其全称为 American Standard Code for Information Interchange(美国标准信息交换代码)。它使用 7 位二进制数(一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的字符。如图 3-6 所示,ASCII 码包括英文字母的大小写、数字 0 ~ 9、一些标点符号,以及一些控制字符(如换行符和制表符)。 +ASCII(码)是最早出现的字符集,其全称为 American Standard Code for Information Interchange(美国标准信息交换代码)。它使用 7 位二进制数(一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的字符。如图 3-6 所示,ASCII 码包括英文字母的大小写、数字 0 ~ 9、一些标点符号,以及一些控制字符(如换行符和制表符)。 ![ASCII 码](character_encoding.assets/ascii_table.png){ class="animation-figure" }图 3-6 ASCII 码
-然而,**ASCII 码仅能够表示英文**。随着计算机的全球化,诞生了一种能够表示更多语言的「EASCII」字符集。它在 ASCII 的 7 位基础上扩展到 8 位,能够表示 256 个不同的字符。 +然而,**ASCII 码仅能够表示英文**。随着计算机的全球化,诞生了一种能够表示更多语言的EASCII字符集。它在 ASCII 的 7 位基础上扩展到 8 位,能够表示 256 个不同的字符。 在世界范围内,陆续出现了一批适用于不同地区的 EASCII 字符集。这些字符集的前 128 个字符统一为 ASCII 码,后 128 个字符定义不同,以适应不同语言的需求。 ## 3.4.2 GBK 字符集 -后来人们发现,**EASCII 码仍然无法满足许多语言的字符数量要求**。比如汉字有近十万个,光日常使用的就有几千个。中国国家标准总局于 1980 年发布了「GB2312」字符集,其收录了 6763 个汉字,基本满足了汉字的计算机处理需要。 +后来人们发现,**EASCII 码仍然无法满足许多语言的字符数量要求**。比如汉字有近十万个,光日常使用的就有几千个。中国国家标准总局于 1980 年发布了GB2312字符集,其收录了 6763 个汉字,基本满足了汉字的计算机处理需要。 -然而,GB2312 无法处理部分罕见字和繁体字。「GBK」字符集是在 GB2312 的基础上扩展得到的,它共收录了 21886 个汉字。在 GBK 的编码方案中,ASCII 字符使用一个字节表示,汉字使用两个字节表示。 +然而,GB2312 无法处理部分罕见字和繁体字。GBK字符集是在 GB2312 的基础上扩展得到的,它共收录了 21886 个汉字。在 GBK 的编码方案中,ASCII 字符使用一个字节表示,汉字使用两个字节表示。 ## 3.4.3 Unicode 字符集 @@ -30,7 +30,7 @@ comments: true 那个时代的研究人员就在想:**如果推出一个足够完整的字符集,将世界范围内的所有语言和符号都收录其中,不就可以解决跨语言环境和乱码问题了吗**?在这种想法的驱动下,一个大而全的字符集 Unicode 应运而生。 -「Unicode」的中文名称为“统一码”,理论上能容纳 100 多万个字符。它致力于将全球范围内的字符纳入统一的字符集之中,提供一种通用的字符集来处理和显示各种语言文字,减少因为编码标准不同而产生的乱码问题。 +Unicode的中文名称为“统一码”,理论上能容纳 100 多万个字符。它致力于将全球范围内的字符纳入统一的字符集之中,提供一种通用的字符集来处理和显示各种语言文字,减少因为编码标准不同而产生的乱码问题。 自 1991 年发布以来,Unicode 不断扩充新的语言与字符。截至 2022 年 9 月,Unicode 已经包含 149186 个字符,包括各种语言的字符、符号甚至表情符号等。在庞大的 Unicode 字符集中,常用的字符占用 2 字节,有些生僻的字符占用 3 字节甚至 4 字节。 diff --git a/docs/chapter_data_structure/number_encoding.md b/docs/chapter_data_structure/number_encoding.md index b6c11d682..9c5361181 100644 --- a/docs/chapter_data_structure/number_encoding.md +++ b/docs/chapter_data_structure/number_encoding.md @@ -24,7 +24,7 @@ comments: true图 3-4 原码、反码与补码之间的相互转换
-「原码 sign-magnitude」虽然最直观,但存在一些局限性。一方面,**负数的原码不能直接用于运算**。例如在原码下计算 $1 + (-2)$ ,得到的结果是 $-3$ ,这显然是不对的。 +原码(sign-magnitude)虽然最直观,但存在一些局限性。一方面,**负数的原码不能直接用于运算**。例如在原码下计算 $1 + (-2)$ ,得到的结果是 $-3$ ,这显然是不对的。 $$ \begin{aligned} @@ -35,7 +35,7 @@ $$ \end{aligned} $$ -为了解决此问题,计算机引入了「反码 1's complement」。如果我们先将原码转换为反码,并在反码下计算 $1 + (-2)$ ,最后将结果从反码转换回原码,则可得到正确结果 $-1$ 。 +为了解决此问题,计算机引入了反码(1's complement)。如果我们先将原码转换为反码,并在反码下计算 $1 + (-2)$ ,最后将结果从反码转换回原码,则可得到正确结果 $-1$ 。 $$ \begin{aligned} @@ -57,7 +57,7 @@ $$ \end{aligned} $$ -与原码一样,反码也存在正负零歧义问题,因此计算机进一步引入了「补码 2's complement」。我们先来观察一下负零的原码、反码、补码的转换过程: +与原码一样,反码也存在正负零歧义问题,因此计算机进一步引入了补码(2's complement)。我们先来观察一下负零的原码、反码、补码的转换过程: $$ \begin{aligned} diff --git a/docs/chapter_divide_and_conquer/divide_and_conquer.md b/docs/chapter_divide_and_conquer/divide_and_conquer.md index 210a13437..1c04dcd66 100644 --- a/docs/chapter_divide_and_conquer/divide_and_conquer.md +++ b/docs/chapter_divide_and_conquer/divide_and_conquer.md @@ -4,7 +4,7 @@ comments: true # 12.1 分治算法 -「分治 divide and conquer」,全称分而治之,是一种非常重要且常见的算法策略。分治通常基于递归实现,包括“分”和“治”两个步骤。 +分治(divide and conquer),全称分而治之,是一种非常重要且常见的算法策略。分治通常基于递归实现,包括“分”和“治”两个步骤。 1. **分(划分阶段)**:递归地将原问题分解为两个或多个子问题,直至到达最小子问题时终止。 2. **治(合并阶段)**:从已知解的最小子问题开始,从底至顶地将子问题的解进行合并,从而构建出原问题的解。 diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md index 65f257fdb..29cdb1931 100644 --- a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md +++ b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -4,7 +4,7 @@ comments: true # 14.1 初探动态规划 -「动态规划 dynamic programming」是一个重要的算法范式,它将一个问题分解为一系列更小的子问题,并通过存储子问题的解来避免重复计算,从而大幅提升时间效率。 +动态规划(dynamic programming)是一个重要的算法范式,它将一个问题分解为一系列更小的子问题,并通过存储子问题的解来避免重复计算,从而大幅提升时间效率。 在本节中,我们从一个经典例题入手,先给出它的暴力回溯解法,观察其中包含的重叠子问题,再逐步导出更高效的动态规划解法。 @@ -1396,9 +1396,9 @@ $$ 根据以上内容,我们可以总结出动态规划的常用术语。 -- 将数组 `dp` 称为「$dp$ 表」,$dp[i]$ 表示状态 $i$ 对应子问题的解。 -- 将最小子问题对应的状态(第 $1$ 阶和第 $2$ 阶楼梯)称为「初始状态」。 -- 将递推公式 $dp[i] = dp[i-1] + dp[i-2]$ 称为「状态转移方程」。 +- 将数组 `dp` 称为$dp$(表),$dp[i]$ 表示状态 $i$ 对应子问题的解。 +- 将最小子问题对应的状态(第 $1$ 阶和第 $2$ 阶楼梯)称为初始状态。 +- 将递推公式 $dp[i] = dp[i-1] + dp[i-2]$ 称为状态转移方程。 ## 14.1.4 空间优化 diff --git a/docs/chapter_graph/graph.md b/docs/chapter_graph/graph.md index bcb32faad..49c9fe6bb 100644 --- a/docs/chapter_graph/graph.md +++ b/docs/chapter_graph/graph.md @@ -4,7 +4,7 @@ comments: true # 9.1 图 -「图 graph」是一种非线性数据结构,由「顶点 vertex」和「边 edge」组成。我们可以将图 $G$ 抽象地表示为一组顶点 $V$ 和一组边 $E$ 的集合。以下示例展示了一个包含 5 个顶点和 7 条边的图。 +图(graph)是一种非线性数据结构,由顶点(vertex)和边(edge)组成。我们可以将图 $G$ 抽象地表示为一组顶点 $V$ 和一组边 $E$ 的集合。以下示例展示了一个包含 5 个顶点和 7 条边的图。 $$ \begin{aligned} @@ -22,7 +22,7 @@ $$ ## 9.1.1 图的常见类型与术语 -根据边是否具有方向,可分为「无向图 undirected graph」和「有向图 directed graph」,如图 9-2 所示。 +根据边是否具有方向,可分为无向图(undirected graph)和有向图(directed graph),如图 9-2 所示。 - 在无向图中,边表示两顶点之间的“双向”连接关系,例如微信或 QQ 中的“好友关系”。 - 在有向图中,边具有方向性,即 $A \rightarrow B$ 和 $A \leftarrow B$ 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系。 @@ -31,7 +31,7 @@ $$图 9-2 有向图与无向图
-根据所有顶点是否连通,可分为「连通图 connected graph」和「非连通图 disconnected graph」,如图 9-3 所示。 +根据所有顶点是否连通,可分为连通图(connected graph)和非连通图(disconnected graph),如图 9-3 所示。 - 对于连通图,从某个顶点出发,可以到达其余任意顶点。 - 对于非连通图,从某个顶点出发,至少有一个顶点无法到达。 @@ -40,7 +40,7 @@ $$图 9-3 连通图与非连通图
-我们还可以为边添加“权重”变量,从而得到如图 9-4 所示的「有权图 weighted graph」。例如在《王者荣耀》等手游中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以用有权图来表示。 +我们还可以为边添加“权重”变量,从而得到如图 9-4 所示的有权图(weighted graph)。例如在《王者荣耀》等手游中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以用有权图来表示。 ![有权图与无权图](graph.assets/weighted_graph.png){ class="animation-figure" } @@ -48,9 +48,9 @@ $$ 图数据结构包含以下常用术语。 -- 「邻接 adjacency」:当两顶点之间存在边相连时,称这两顶点“邻接”。在图 9-4 中,顶点 1 的邻接顶点为顶点 2、3、5。 -- 「路径 path」:从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径”。在图 9-4 中,边序列 1-5-2-4 是顶点 1 到顶点 4 的一条路径。 -- 「度 degree」:一个顶点拥有的边数。对于有向图,「入度 in-degree」表示有多少条边指向该顶点,「出度 out-degree」表示有多少条边从该顶点指出。 +- 邻接(adjacency):当两顶点之间存在边相连时,称这两顶点“邻接”。在图 9-4 中,顶点 1 的邻接顶点为顶点 2、3、5。 +- 路径(path):从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径”。在图 9-4 中,边序列 1-5-2-4 是顶点 1 到顶点 4 的一条路径。 +- 度(degree):一个顶点拥有的边数。对于有向图,入度(in-degree)表示有多少条边指向该顶点,出度(out-degree)表示有多少条边从该顶点指出。 ## 9.1.2 图的表示 @@ -58,7 +58,7 @@ $$ ### 1. 邻接矩阵 -设图的顶点数量为 $n$ ,「邻接矩阵 adjacency matrix」使用一个 $n \times n$ 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 $1$ 或 $0$ 表示两个顶点之间是否存在边。 +设图的顶点数量为 $n$ ,邻接矩阵(adjacency matrix)使用一个 $n \times n$ 大小的矩阵来表示图,每一行(列)代表一个顶点,矩阵元素代表边,用 $1$ 或 $0$ 表示两个顶点之间是否存在边。 如图 9-5 所示,设邻接矩阵为 $M$、顶点列表为 $V$ ,那么矩阵元素 $M[i, j] = 1$ 表示顶点 $V[i]$ 到顶点 $V[j]$ 之间存在边,反之 $M[i, j] = 0$ 表示两顶点之间无边。 @@ -76,7 +76,7 @@ $$ ### 2. 邻接表 -「邻接表 adjacency list」使用 $n$ 个链表来表示图,链表节点表示顶点。第 $i$ 个链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(与该顶点相连的顶点)。图 9-6 展示了一个使用邻接表存储的图的示例。 +邻接表(adjacency list)使用 $n$ 个链表来表示图,链表节点表示顶点。第 $i$ 个链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(与该顶点相连的顶点)。图 9-6 展示了一个使用邻接表存储的图的示例。 ![图的邻接表表示](graph.assets/adjacency_list.png){ class="animation-figure" } diff --git a/docs/chapter_graph/graph_traversal.md b/docs/chapter_graph/graph_traversal.md index be078a275..40b874271 100644 --- a/docs/chapter_graph/graph_traversal.md +++ b/docs/chapter_graph/graph_traversal.md @@ -6,7 +6,7 @@ comments: true 树代表的是“一对多”的关系,而图则具有更高的自由度,可以表示任意的“多对多”关系。因此,我们可以把树看作图的一种特例。显然,**树的遍历操作也是图的遍历操作的一种特例**。 -图和树都需要应用搜索算法来实现遍历操作。图的遍历方式也可分为两种:「广度优先遍历」和「深度优先遍历」。 +图和树都需要应用搜索算法来实现遍历操作。图的遍历方式也可分为两种:广度优先遍历和深度优先遍历。 ## 9.3.1 广度优先遍历 diff --git a/docs/chapter_greedy/greedy_algorithm.md b/docs/chapter_greedy/greedy_algorithm.md index 3ba3cbb4d..963f0dc23 100644 --- a/docs/chapter_greedy/greedy_algorithm.md +++ b/docs/chapter_greedy/greedy_algorithm.md @@ -4,7 +4,7 @@ comments: true # 15.1 贪心算法 -「贪心算法 greedy algorithm」是一种常见的解决优化问题的算法,其基本思想是在问题的每个决策阶段,都选择当前看起来最优的选择,即贪心地做出局部最优的决策,以期获得全局最优解。贪心算法简洁且高效,在许多实际问题中有着广泛的应用。 +贪心算法(greedy algorithm)是一种常见的解决优化问题的算法,其基本思想是在问题的每个决策阶段,都选择当前看起来最优的选择,即贪心地做出局部最优的决策,以期获得全局最优解。贪心算法简洁且高效,在许多实际问题中有着广泛的应用。 贪心算法和动态规划都常用于解决优化问题。它们之间存在一些相似之处,比如都依赖最优子结构性质,但工作原理不同。 diff --git a/docs/chapter_hashing/hash_collision.md b/docs/chapter_hashing/hash_collision.md index c32f33364..47bd6748e 100644 --- a/docs/chapter_hashing/hash_collision.md +++ b/docs/chapter_hashing/hash_collision.md @@ -15,7 +15,7 @@ comments: true ## 6.2.1 链式地址 -在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 separate chaining」将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。图 6-5 展示了一个链式地址哈希表的例子。 +在原始哈希表中,每个桶仅能存储一个键值对。链式地址(separate chaining)将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。图 6-5 展示了一个链式地址哈希表的例子。 ![链式地址哈希表](hash_collision.assets/hash_table_chaining.png){ class="animation-figure" } @@ -1447,7 +1447,7 @@ comments: true ## 6.2.2 开放寻址 -「开放寻址 open addressing」不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测和多次哈希等。 +开放寻址(open addressing)不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测和多次哈希等。 下面以线性探测为例,介绍开放寻址哈希表的工作机制。 @@ -1472,7 +1472,7 @@ comments: true图 6-7 在开放寻址中删除元素导致的查询问题
-为了解决该问题,我们可以采用「懒删除 lazy deletion」机制:它不直接从哈希表中移除元素,**而是利用一个常量 `TOMBSTONE` 来标记这个桶**。在该机制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置键值对。但不同的是,线性探测到 `TOMBSTONE` 时应该继续遍历,因为其之下可能还存在键值对。 +为了解决该问题,我们可以采用懒删除(lazy deletion)机制:它不直接从哈希表中移除元素,**而是利用一个常量 `TOMBSTONE` 来标记这个桶**。在该机制下,`None` 和 `TOMBSTONE` 都代表空桶,都可以放置键值对。但不同的是,线性探测到 `TOMBSTONE` 时应该继续遍历,因为其之下可能还存在键值对。 然而,**懒删除可能会加速哈希表的性能退化**。这是因为每次删除操作都会产生一个删除标记,随着 `TOMBSTONE` 的增加,搜索时间也会增加,因为线性探测可能需要跳过多个 `TOMBSTONE` 才能找到目标元素。 diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index 36988ae09..d097859df 100755 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -4,7 +4,7 @@ comments: true # 6.1 哈希表 -「哈希表 hash table」,又称「散列表」,它通过建立键 `key` 与值 `value` 之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键 `key` ,则可以在 $O(1)$ 时间内获取对应的值 `value` 。 +哈希表(hash table),又称散列表,它通过建立键 `key` 与值 `value` 之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键 `key` ,则可以在 $O(1)$ 时间内获取对应的值 `value` 。 如图 6-1 所示,给定 $n$ 个学生,每个学生都有“姓名”和“学号”两项数据。假如我们希望实现“输入一个学号,返回对应的姓名”的查询功能,则可以采用图 6-1 所示的哈希表来实现。 @@ -539,9 +539,9 @@ comments: true ## 6.1.2 哈希表简单实现 -我们先考虑最简单的情况,**仅用一个数组来实现哈希表**。在哈希表中,我们将数组中的每个空位称为「桶 bucket」,每个桶可存储一个键值对。因此,查询操作就是找到 `key` 对应的桶,并在桶中获取 `value` 。 +我们先考虑最简单的情况,**仅用一个数组来实现哈希表**。在哈希表中,我们将数组中的每个空位称为桶(bucket),每个桶可存储一个键值对。因此,查询操作就是找到 `key` 对应的桶,并在桶中获取 `value` 。 -那么,如何基于 `key` 定位对应的桶呢?这是通过「哈希函数 hash function」实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有 `key` ,输出空间是所有桶(数组索引)。换句话说,输入一个 `key` ,**我们可以通过哈希函数得到该 `key` 对应的键值对在数组中的存储位置**。 +那么,如何基于 `key` 定位对应的桶呢?这是通过哈希函数(hash function)实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有 `key` ,输出空间是所有桶(数组索引)。换句话说,输入一个 `key` ,**我们可以通过哈希函数得到该 `key` 对应的键值对在数组中的存储位置**。 输入一个 `key` ,哈希函数的计算过程分为以下两步。 @@ -1871,7 +1871,7 @@ index = hash(key) % capacity 20336 % 100 = 36 ``` -如图 6-3 所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为「哈希冲突 hash collision」。 +如图 6-3 所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为哈希冲突(hash collision)。 ![哈希冲突示例](hash_map.assets/hash_collision.png){ class="animation-figure" } @@ -1887,4 +1887,4 @@ index = hash(key) % capacity 类似于数组扩容,哈希表扩容需将所有键值对从原哈希表迁移至新哈希表,非常耗时;并且由于哈希表容量 `capacity` 改变,我们需要通过哈希函数来重新计算所有键值对的存储位置,这进一步增加了扩容过程的计算开销。为此,编程语言通常会预留足够大的哈希表容量,防止频繁扩容。 -「负载因子 load factor」是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,**也常作为哈希表扩容的触发条件**。例如在 Java 中,当负载因子超过 $0.75$ 时,系统会将哈希表扩容至原先的 $2$ 倍。 +负载因子(load factor)是哈希表的一个重要概念,其定义为哈希表的元素数量除以桶数量,用于衡量哈希冲突的严重程度,**也常作为哈希表扩容的触发条件**。例如在 Java 中,当负载因子超过 $0.75$ 时,系统会将哈希表扩容至原先的 $2$ 倍。 diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md index 1e84af714..00e886c14 100644 --- a/docs/chapter_heap/heap.md +++ b/docs/chapter_heap/heap.md @@ -4,10 +4,10 @@ comments: true # 8.1 堆 -「堆 heap」是一种满足特定条件的完全二叉树,主要可分为两种类型,如图 8-1 所示。 +堆(heap)是一种满足特定条件的完全二叉树,主要可分为两种类型,如图 8-1 所示。 -- 「小顶堆 min heap」:任意节点的值 $\leq$ 其子节点的值。 -- 「大顶堆 max heap」:任意节点的值 $\geq$ 其子节点的值。 +- 小顶堆(min heap):任意节点的值 $\leq$ 其子节点的值。 +- 大顶堆(max heap):任意节点的值 $\geq$ 其子节点的值。 ![小顶堆与大顶堆](heap.assets/min_heap_and_max_heap.png){ class="animation-figure" } @@ -21,7 +21,7 @@ comments: true ## 8.1.1 堆的常用操作 -需要指出的是,许多编程语言提供的是「优先队列 priority queue」,这是一种抽象的数据结构,定义为具有优先级排序的队列。 +需要指出的是,许多编程语言提供的是优先队列(priority queue),这是一种抽象的数据结构,定义为具有优先级排序的队列。 实际上,**堆通常用于实现优先队列,大顶堆相当于元素按从大到小的顺序出队的优先队列**。从使用角度来看,我们可以将“优先队列”和“堆”看作等价的数据结构。因此,本书对两者不做特别区分,统一称作“堆”。 @@ -836,7 +836,7 @@ comments: true ### 3. 元素入堆 -给定元素 `val` ,我们首先将其添加到堆底。添加之后,由于 `val` 可能大于堆中其他元素,堆的成立条件可能已被破坏,**因此需要修复从插入节点到根节点的路径上的各个节点**,这个操作被称为「堆化 heapify」。 +给定元素 `val` ,我们首先将其添加到堆底。添加之后,由于 `val` 可能大于堆中其他元素,堆的成立条件可能已被破坏,**因此需要修复从插入节点到根节点的路径上的各个节点**,这个操作被称为堆化(heapify)。 考虑从入堆节点开始,**从底至顶执行堆化**。如图 8-3 所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。 diff --git a/docs/chapter_hello_algo/index.md b/docs/chapter_hello_algo/index.md new file mode 100644 index 000000000..2f7a89379 --- /dev/null +++ b/docs/chapter_hello_algo/index.md @@ -0,0 +1,30 @@ +--- +comments: true +icon: material/rocket-launch-outline +--- + +# 序 + +几年前,我在力扣上分享了“剑指 Offer”系列题解,受到了许多读者的鼓励和支持。在与读者交流期间,我最常被问的一个问题是“如何入门算法”。逐渐地,我对这个问题产生了浓厚的兴趣。 + +两眼一抹黑地刷题似乎是最受欢迎的方法,简单、直接且有效。然而刷题就如同玩“扫雷”游戏,自学能力强的人能够顺利将地雷逐个排掉,而基础不足的人很可能被炸得满头是包,并在挫折中步步退缩。通读教材也是一种常见做法,但对于面向求职的人来说,毕业论文、投递简历、准备笔试和面试已经消耗了大部分精力,啃厚重的书往往变成了一项艰巨的挑战。 + +如果你也面临类似的困扰,那么很幸运这本书“找”到了你。本书是我对这个问题给出的答案,即使不是最优解,也至少是一次积极的尝试。本书虽然不足以让你直接拿到 Offer,但会引导你探索数据结构与算法的“知识地图”,带你了解不同“地雷”的形状、大小和分布位置,让你掌握各种“排雷方法”。有了这些本领,相信你可以更加自如地刷题和阅读文献,逐步构建起完整的知识体系。 + +我深深赞同费曼教授所言:“Knowledge isn't free. You have to pay attention.”从这个意义上看,这本书并非完全“免费”。为了不辜负你为本书所付出的宝贵“注意力”,我会竭尽所能,投入最大的“注意力”来完成本书的创作。 + +本人自知学疏才浅,书中内容虽然已经过一段时间的打磨,但一定仍有许多错误,恳请各位老师和同学批评指正。 + +![Hello 算法](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" } + +图 7-25 AVL 树在插入节点后发生退化
-1962 年 G. M. Adelson-Velsky 和 E. M. Landis 在论文“An algorithm for the organization of information”中提出了「AVL 树」。论文中详细描述了一系列操作,确保在持续添加和删除节点后,AVL 树不会退化,从而使得各种操作的时间复杂度保持在 $O(\log n)$ 级别。换句话说,在需要频繁进行增删查改操作的场景中,AVL 树能始终保持高效的数据操作性能,具有很好的应用价值。 +1962 年 G. M. Adelson-Velsky 和 E. M. Landis 在论文“An algorithm for the organization of information”中提出了AVL(树)。论文中详细描述了一系列操作,确保在持续添加和删除节点后,AVL 树不会退化,从而使得各种操作的时间复杂度保持在 $O(\log n)$ 级别。换句话说,在需要频繁进行增删查改操作的场景中,AVL 树能始终保持高效的数据操作性能,具有很好的应用价值。 ## 7.5.1 AVL 树常见术语 -AVL 树既是二叉搜索树,也是平衡二叉树,同时满足这两类二叉树的所有性质,因此是一种「平衡二叉搜索树 balanced binary search tree」。 +AVL 树既是二叉搜索树,也是平衡二叉树,同时满足这两类二叉树的所有性质,因此是一种平衡二叉搜索树(balanced binary search tree)。 ### 1. 节点高度 @@ -479,7 +479,7 @@ AVL 树既是二叉搜索树,也是平衡二叉树,同时满足这两类二 ### 2. 节点平衡因子 -节点的「平衡因子 balance factor」定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 $0$ 。我们同样将获取节点平衡因子的功能封装成函数,方便后续使用: +节点的平衡因子(balance factor)定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 $0$ 。我们同样将获取节点平衡因子的功能封装成函数,方便后续使用: === "Python" diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index fc6e83eff..1a644d8e5 100755 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -4,7 +4,7 @@ comments: true # 7.4 二叉搜索树 -如图 7-16 所示,「二叉搜索树 binary search tree」满足以下条件。 +如图 7-16 所示,二叉搜索树(binary search tree)满足以下条件。 1. 对于根节点,左子树中所有节点的值 $<$ 根节点的值 $<$ 右子树中所有节点的值。 2. 任意节点的左、右子树也是二叉搜索树,即同样满足条件 `1.` 。 diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 556a3b892..d904fbd5a 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -4,7 +4,7 @@ comments: true # 7.1 二叉树 -「二叉树 binary tree」是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二”的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。 +二叉树(binary tree)是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二”的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。 === "Python" @@ -202,7 +202,7 @@ comments: true ``` -每个节点都有两个引用(指针),分别指向「左子节点 left-child node」和「右子节点 right-child node」,该节点被称为这两个子节点的「父节点 parent node」。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的「左子树 left subtree」,同理可得「右子树 right subtree」。 +每个节点都有两个引用(指针),分别指向左子节点(left-child node)和右子节点(right-child node),该节点被称为这两个子节点的父节点(parent node)。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的左子树(left subtree),同理可得右子树(right subtree)。 **在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树**。如图 7-1 所示,如果将“节点 2”视为父节点,则其左子节点和右子节点分别是“节点 4”和“节点 5”,左子树是“节点 4 及其以下节点形成的树”,右子树是“节点 5 及其以下节点形成的树”。 @@ -214,14 +214,14 @@ comments: true 二叉树的常用术语如图 7-2 所示。 -- 「根节点 root node」:位于二叉树顶层的节点,没有父节点。 -- 「叶节点 leaf node」:没有子节点的节点,其两个指针均指向 `None` 。 -- 「边 edge」:连接两个节点的线段,即节点引用(指针)。 -- 节点所在的「层 level」:从顶至底递增,根节点所在层为 1 。 -- 节点的「度 degree」:节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。 -- 二叉树的「高度 height」:从根节点到最远叶节点所经过的边的数量。 -- 节点的「深度 depth」:从根节点到该节点所经过的边的数量。 -- 节点的「高度 height」:从距离该节点最远的叶节点到该节点所经过的边的数量。 +- 根节点(root node):位于二叉树顶层的节点,没有父节点。 +- 叶节点(leaf node):没有子节点的节点,其两个指针均指向 `None` 。 +- 边(edge):连接两个节点的线段,即节点引用(指针)。 +- 节点所在的层(level):从顶至底递增,根节点所在层为 1 。 +- 节点的度(degree):节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。 +- 二叉树的高度(height):从根节点到最远叶节点所经过的边的数量。 +- 节点的深度(depth):从根节点到该节点所经过的边的数量。 +- 节点的高度(height):从距离该节点最远的叶节点到该节点所经过的边的数量。 ![二叉树的常用术语](binary_tree.assets/binary_tree_terminology.png){ class="animation-figure" } @@ -627,11 +627,11 @@ comments: true ### 1. 完美二叉树 -如图 7-4 所示,「完美二叉树 perfect binary tree」所有层的节点都被完全填满。在完美二叉树中,叶节点的度为 $0$ ,其余所有节点的度都为 $2$ ;若树的高度为 $h$ ,则节点总数为 $2^{h+1} - 1$ ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。 +如图 7-4 所示,完美二叉树(perfect binary tree)所有层的节点都被完全填满。在完美二叉树中,叶节点的度为 $0$ ,其余所有节点的度都为 $2$ ;若树的高度为 $h$ ,则节点总数为 $2^{h+1} - 1$ ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。 !!! tip - 请注意,在中文社区中,完美二叉树常被称为「满二叉树」。 + 请注意,在中文社区中,完美二叉树常被称为满二叉树。 ![完美二叉树](binary_tree.assets/perfect_binary_tree.png){ class="animation-figure" } @@ -639,7 +639,7 @@ comments: true ### 2. 完全二叉树 -如图 7-5 所示,「完全二叉树 complete binary tree」只有最底层的节点未被填满,且最底层节点尽量靠左填充。 +如图 7-5 所示,完全二叉树(complete binary tree)只有最底层的节点未被填满,且最底层节点尽量靠左填充。 ![完全二叉树](binary_tree.assets/complete_binary_tree.png){ class="animation-figure" } @@ -647,7 +647,7 @@ comments: true ### 3. 完满二叉树 -如图 7-6 所示,「完满二叉树 full binary tree」除了叶节点之外,其余所有节点都有两个子节点。 +如图 7-6 所示,完满二叉树(full binary tree)除了叶节点之外,其余所有节点都有两个子节点。 ![完满二叉树](binary_tree.assets/full_binary_tree.png){ class="animation-figure" } @@ -655,7 +655,7 @@ comments: true ### 4. 平衡二叉树 -如图 7-7 所示,「平衡二叉树 balanced binary tree」中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。 +如图 7-7 所示,平衡二叉树(balanced binary tree)中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。 ![平衡二叉树](binary_tree.assets/balanced_binary_tree.png){ class="animation-figure" } diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index 6f36b275d..d5bb75cfb 100755 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -10,9 +10,9 @@ comments: true ## 7.2.1 层序遍历 -如图 7-9 所示,「层序遍历 level-order traversal」从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。 +如图 7-9 所示,层序遍历(level-order traversal)从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。 -层序遍历本质上属于「广度优先遍历 breadth-first traversal」,也称「广度优先搜索 breadth-first search, BFS」,它体现了一种“一圈一圈向外扩展”的逐层遍历方式。 +层序遍历本质上属于广度优先遍历(breadth-first traversal),也称广度优先搜索(breadth-first search, BFS),它体现了一种“一圈一圈向外扩展”的逐层遍历方式。 ![二叉树的层序遍历](binary_tree_traversal.assets/binary_tree_bfs.png){ class="animation-figure" } @@ -364,7 +364,7 @@ comments: true ## 7.2.2 前序、中序、后序遍历 -相应地,前序、中序和后序遍历都属于「深度优先遍历 depth-first traversal」,也称「深度优先搜索 depth-first search, DFS」,它体现了一种“先走到尽头,再回溯继续”的遍历方式。 +相应地,前序、中序和后序遍历都属于深度优先遍历(depth-first traversal),也称深度优先搜索(depth-first search, DFS),它体现了一种“先走到尽头,再回溯继续”的遍历方式。 图 7-10 展示了对二叉树进行深度优先遍历的工作原理。**深度优先遍历就像是绕着整棵二叉树的外围“走”一圈**,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。 diff --git a/docs/index.html b/docs/index.html index 52d3dbd66..0bcbe8996 100644 --- a/docs/index.html +++ b/docs/index.html @@ -354,17 +354,4 @@ - - - -欢迎在评论区留下你的见解、问题或建议
- - -