From 98d1244e32df673959deb2ddd1484b72ed61d1c8 Mon Sep 17 00:00:00 2001 From: dumingyu Date: Fri, 6 Jan 2023 11:00:12 +0800 Subject: [PATCH 01/21] fix(codes/cpp): add climits headers This fixes clang++ compile error when using INT_MAX in PrintUtil. --- codes/cpp/include/PrintUtil.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/codes/cpp/include/PrintUtil.hpp b/codes/cpp/include/PrintUtil.hpp index 06397855b..e788d1650 100644 --- a/codes/cpp/include/PrintUtil.hpp +++ b/codes/cpp/include/PrintUtil.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "ListNode.hpp" #include "TreeNode.hpp" From 0b778f27a112824e21f1b8b8e3b16142868a4008 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sat, 7 Jan 2023 17:12:25 +0800 Subject: [PATCH 02/21] Update time complexity. --- .../performance_evaluation.md | 2 +- docs/chapter_computational_complexity/time_complexity.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/chapter_computational_complexity/performance_evaluation.md b/docs/chapter_computational_complexity/performance_evaluation.md index c664a2083..3244f41d8 100644 --- a/docs/chapter_computational_complexity/performance_evaluation.md +++ b/docs/chapter_computational_complexity/performance_evaluation.md @@ -16,7 +16,7 @@ comments: true - **时间效率** ,即算法的运行速度的快慢。 - **空间效率** ,即算法占用的内存空间大小。 -数据结构与算法追求“运行得快、内存占用少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。 +数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。 ## 效率评估方法 diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 42d43624b..03e59e256 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -365,9 +365,9 @@ $$ **时间复杂度可以有效评估算法效率。** 算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。 -**时间复杂度分析将统计「计算操作的运行时间」简化为统计「计算操作的数量」。** 这是因为,无论是运行平台、还是计算操作类型,都与算法运行时间的增长趋势无关。因此,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”。 +**时间复杂度的推算方法更加简便。** 在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。 -**时间复杂度也存在一定的局限性。** 比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。即使存在这些问题,计算复杂度仍然是评判算法效率的最有效、最常用方法。 +**时间复杂度也存在一定的局限性。** 比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。 ## 函数渐近上界 From 694ea4f665733ca81fa0a21f72c5bae2f5aae8c5 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sat, 7 Jan 2023 20:34:32 +0800 Subject: [PATCH 03/21] =?UTF-8?q?Modify=20`=E3=80=82**=20`=20to=20`**?= =?UTF-8?q?=E3=80=82`=20for=20better=20visualization.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/chapter_array_and_linkedlist/array.md | 18 +-- .../linked_list.md | 16 +-- docs/chapter_array_and_linkedlist/list.md | 14 +- .../performance_evaluation.md | 10 +- .../space_complexity.md | 6 +- .../space_time_tradeoff.md | 2 +- .../time_complexity.md | 18 +-- .../classification_of_data_structure.md | 6 +- .../chapter_data_structure/data_and_memory.md | 6 +- docs/chapter_heap/heap.md | 32 +++++ .../algorithms_are_everywhere.md | 6 +- docs/chapter_preface/about_the_book.md | 6 +- docs/chapter_preface/suggestions.md | 6 +- docs/chapter_searching/binary_search.md | 10 +- docs/chapter_searching/linear_search.md | 4 +- docs/chapter_sorting/quick_sort.md | 4 +- docs/chapter_stack_and_queue/deque.md | 58 ++++---- docs/chapter_stack_and_queue/queue.md | 104 +++++++------- docs/chapter_stack_and_queue/stack.md | 132 +++++++++--------- docs/chapter_tree/avl_tree.md | 2 +- docs/chapter_tree/binary_search_tree.md | 6 +- docs/chapter_tree/binary_tree.md | 6 +- 22 files changed, 252 insertions(+), 220 deletions(-) create mode 100644 docs/chapter_heap/heap.md diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 648a9c753..8befdbea6 100644 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -14,7 +14,7 @@ comments: true 观察上图,我们发现 **数组首元素的索引为 $0$** 。你可能会想,这并不符合日常习惯,首个元素的索引为什么不是 $1$ 呢,这不是更加自然吗?我认同你的想法,但请先记住这个设定,后面讲内存地址计算时,我会尝试解答这个问题。 -**数组有多种初始化写法。** 根据实际需要,选代码最短的那一种就好。 +**数组有多种初始化写法**。根据实际需要,选代码最短的那一种就好。 === "Java" @@ -83,7 +83,7 @@ comments: true ## 数组优点 -**在数组中访问元素非常高效。** 这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。 +**在数组中访问元素非常高效**。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。 ![array_memory_location_calculation](array.assets/array_memory_location_calculation.png) @@ -195,7 +195,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex ## 数组缺点 -**数组在初始化后长度不可变。** 由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。 +**数组在初始化后长度不可变**。由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。 === "Java" @@ -317,7 +317,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -**数组中插入或删除元素效率低下。** 假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点: +**数组中插入或删除元素效率低下**。假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点: - **时间复杂度高:** 数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。 - **丢失元素:** 由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。 @@ -488,7 +488,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex ## 数组常用操作 -**数组遍历。** 以下介绍两种常用的遍历方法。 +**数组遍历**。以下介绍两种常用的遍历方法。 === "Java" @@ -611,7 +611,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` -**数组查找。** 通过遍历数组,查找数组内的指定元素,并输出对应索引。 +**数组查找**。通过遍历数组,查找数组内的指定元素,并输出对应索引。 === "Java" @@ -715,8 +715,8 @@ elementAddr = firtstElementAddr + elementLength * elementIndex ## 数组典型应用 -**随机访问。** 如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。 +**随机访问**。如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。 -**二分查找。** 例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。 +**二分查找**。例如前文查字典的例子,我们可以将字典中的所有字按照拼音顺序存储在数组中,然后使用与日常查纸质字典相同的“翻开中间,排除一半”的方式,来实现一个查电子字典的算法。 -**深度学习。** 神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。 +**深度学习**。神经网络中大量使用了向量、矩阵、张量之间的线性代数运算,这些数据都是以数组的形式构建的。数组是神经网络编程中最常使用的数据结构。 diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index 5b6888c06..f054802d4 100644 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -114,7 +114,7 @@ comments: true **尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。 -**链表初始化方法。** 建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。 +**链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。 !!! tip @@ -248,7 +248,7 @@ comments: true ## 链表优点 -**在链表中,插入与删除结点的操作效率高。** 例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 +**在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 ![linkedlist_insert_remove_node](linked_list.assets/linkedlist_insert_remove_node.png) @@ -412,7 +412,7 @@ comments: true ## 链表缺点 -**链表访问结点效率低。** 上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 +**链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 === "Java" @@ -520,11 +520,11 @@ comments: true } ``` -**链表的内存占用多。** 链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 +**链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 ## 链表常用操作 -**遍历链表查找。** 遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。 +**遍历链表查找**。遍历链表,查找链表内值为 `target` 的结点,输出结点在链表中的索引。 === "Java" @@ -649,11 +649,11 @@ comments: true ## 常见链表类型 -**单向链表。** 即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。 +**单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。 -**环形链表。** 如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。 +**环形链表**。如果我们令单向链表的尾结点指向头结点(即首尾相接),则得到一个环形链表。在环形链表中,我们可以将任意结点看作是头结点。 -**双向链表。** 单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。 +**双向链表**。单向链表仅记录了一个方向的指针(引用),在双向链表的结点定义中,同时有指向下一结点(后继结点)和上一结点(前驱结点)的「指针(引用)」。双向链表相对于单向链表更加灵活,即可以朝两个方向遍历链表,但也需要占用更多的内存空间。 === "Java" diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index 749b19482..19de6d778 100644 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -4,13 +4,13 @@ comments: true # 列表 -**由于长度不可变,数组的实用性大大降低。** 在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。 +**由于长度不可变,数组的实用性大大降低**。在很多情况下,我们事先并不知道会输入多少数据,这就为数组长度的选择带来了很大困难。长度选小了,需要在添加数据中频繁地扩容数组;长度选大了,又造成内存空间的浪费。 为了解决此问题,诞生了一种被称为「列表 List」的数据结构。列表可以被理解为长度可变的数组,因此也常被称为「动态数组 Dynamic Array」。列表基于数组实现,继承了数组的优点,同时还可以在程序运行中实时扩容。在列表中,我们可以自由地添加元素,而不用担心超过容量限制。 ## 列表常用操作 -**初始化列表。** 我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。 +**初始化列表**。我们通常会使用到“无初始值”和“有初始值”的两种初始化方法。 === "Java" @@ -91,7 +91,7 @@ comments: true List list = numbers.ToList(); ``` -**访问与更新元素。** 列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 +**访问与更新元素**。列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 === "Java" @@ -169,7 +169,7 @@ comments: true list[1] = 0; // 将索引 1 处的元素更新为 0 ``` -**在列表中添加、插入、删除元素。** 相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 +**在列表中添加、插入、删除元素**。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 === "Java" @@ -317,7 +317,7 @@ comments: true list.RemoveAt(3); ``` -**遍历列表。** 与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 +**遍历列表**。与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 === "Java" @@ -437,7 +437,7 @@ comments: true } ``` -**拼接两个列表。** 再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 +**拼接两个列表**。再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 === "Java" @@ -502,7 +502,7 @@ comments: true list.AddRange(list1); // 将列表 list1 拼接到 list 之后 ``` -**排序列表。** 排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 +**排序列表**。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 === "Java" diff --git a/docs/chapter_computational_complexity/performance_evaluation.md b/docs/chapter_computational_complexity/performance_evaluation.md index 3244f41d8..f75b4d8d4 100644 --- a/docs/chapter_computational_complexity/performance_evaluation.md +++ b/docs/chapter_computational_complexity/performance_evaluation.md @@ -8,8 +8,8 @@ comments: true 在开始学习算法之前,我们首先要想清楚算法的设计目标是什么,或者说,如何来评判算法的好与坏。整体上看,我们设计算法时追求两个层面的目标。 -1. **找到问题解法。** 算法需要能够在规定的输入范围下,可靠地求得问题的正确解。 -2. **寻求最优解法。** 同一个问题可能存在多种解法,而我们希望算法效率尽可能的高。 +1. **找到问题解法**。算法需要能够在规定的输入范围下,可靠地求得问题的正确解。 +2. **寻求最优解法**。同一个问题可能存在多种解法,而我们希望算法效率尽可能的高。 换言之,在可以解决问题的前提下,算法效率则是主要评价维度,包括: @@ -24,9 +24,9 @@ comments: true 假设我们现在有算法 A 和 算法 B ,都能够解决同一问题,现在需要对比两个算法之间的效率。我们能够想到的最直接的方式,就是找一台计算机,把两个算法都完整跑一遍,并监控记录运行时间和内存占用情况。这种评估方式能够反映真实情况,但是也存在很大的硬伤。 -**难以排除测试环境的干扰因素。** 硬件配置会影响到算法的性能表现。例如,在某台计算机中,算法 A 比算法 B 运行时间更短;但换到另一台配置不同的计算机中,可能会得到相反的测试结果。这意味着我们需要在各种机器上展开测试,而这是不现实的。 +**难以排除测试环境的干扰因素**。硬件配置会影响到算法的性能表现。例如,在某台计算机中,算法 A 比算法 B 运行时间更短;但换到另一台配置不同的计算机中,可能会得到相反的测试结果。这意味着我们需要在各种机器上展开测试,而这是不现实的。 -**展开完整测试非常耗费资源。** 随着输入数据量的大小变化,算法会呈现出不同的效率表现。比如,有可能输入数据量较小时,算法 A 运行时间短于算法 B ,而在输入数据量较大时,测试结果截然相反。因此,若想要达到具有说服力的对比结果,那么需要输入各种体量数据,这样的测试需要占用大量计算资源。 +**展开完整测试非常耗费资源**。随着输入数据量的大小变化,算法会呈现出不同的效率表现。比如,有可能输入数据量较小时,算法 A 运行时间短于算法 B ,而在输入数据量较大时,测试结果截然相反。因此,若想要达到具有说服力的对比结果,那么需要输入各种体量数据,这样的测试需要占用大量计算资源。 ### 理论估算 @@ -34,7 +34,7 @@ comments: true **复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势** 。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。 -**复杂度分析克服了实际测试方法的弊端。** 一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。 +**复杂度分析克服了实际测试方法的弊端**。一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。 ## 复杂度分析的重要性 diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index 1d561792b..1756f2e24 100644 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -208,8 +208,8 @@ comments: true **最差空间复杂度中的“最差”有两层含义**,分别为输入数据的最差分布、算法运行中的最差时间点。 -- **以最差输入数据为准。** 当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但是当 $n > 10$ 时,初始化的数组 `nums` 使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; -- **以算法运行过程中的峰值内存为准。** 程序在执行最后一行之前,使用 $O(1)$ 空间;当初始化数组 `nums` 时,程序使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; +- **以最差输入数据为准**。当 $n < 10$ 时,空间复杂度为 $O(1)$ ;但是当 $n > 10$ 时,初始化的数组 `nums` 使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; +- **以算法运行过程中的峰值内存为准**。程序在执行最后一行之前,使用 $O(1)$ 空间;当初始化数组 `nums` 时,程序使用 $O(n)$ 空间;因此最差空间复杂度为 $O(n)$ ; === "Java" @@ -301,7 +301,7 @@ comments: true } ``` -**在递归函数中,需要注意统计栈帧空间。** 例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。 +**在递归函数中,需要注意统计栈帧空间**。例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。 === "Java" diff --git a/docs/chapter_computational_complexity/space_time_tradeoff.md b/docs/chapter_computational_complexity/space_time_tradeoff.md index 0b86c11ca..49cf33778 100644 --- a/docs/chapter_computational_complexity/space_time_tradeoff.md +++ b/docs/chapter_computational_complexity/space_time_tradeoff.md @@ -6,7 +6,7 @@ comments: true 理想情况下,我们希望算法的时间复杂度和空间复杂度都能够达到最优,而实际上,同时优化时间复杂度和空间复杂度是非常困难的。 -**降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然。** 我们把牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,称之为「以时间换空间」。选择哪种思路取决于我们更看重哪个方面。 +**降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然**。我们把牺牲内存空间来提升算法运行速度的思路称为「以空间换时间」;反之,称之为「以时间换空间」。选择哪种思路取决于我们更看重哪个方面。 大多数情况下,时间都是比空间更宝贵的,只要空间复杂度不要太离谱、能接受就行,**因此以空间换时间最为常用**。 diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 03e59e256..217981ae8 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -153,7 +153,7 @@ $$ } ``` -但实际上, **统计算法的运行时间既不合理也不现实。** 首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。 +但实际上, **统计算法的运行时间既不合理也不现实**。首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。 ## 统计时间增长趋势 @@ -363,11 +363,11 @@ $$ 相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足? -**时间复杂度可以有效评估算法效率。** 算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。 +**时间复杂度可以有效评估算法效率**。算法 `B` 运行时间的增长是线性的,在 $n > 1$ 时慢于算法 `A` ,在 $n > 1000000$ 时慢于算法 `C` 。实质上,只要输入数据大小 $n$ 足够大,复杂度为「常数阶」的算法一定优于「线性阶」的算法,这也正是时间增长趋势的含义。 -**时间复杂度的推算方法更加简便。** 在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。 +**时间复杂度的推算方法更加简便**。在时间复杂度分析中,我们可以将统计「计算操作的运行时间」简化为统计「计算操作的数量」,这是因为,无论是运行平台还是计算操作类型,都与算法运行时间的增长趋势无关。因而,我们可以简单地将所有计算操作的执行时间统一看作是相同的“单位时间”,这样的简化做法大大降低了估算难度。 -**时间复杂度也存在一定的局限性。** 比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。 +**时间复杂度也存在一定的局限性**。比如,虽然算法 `A` 和 `C` 的时间复杂度相同,但是实际的运行时间有非常大的差别。再比如,虽然算法 `B` 比 `C` 的时间复杂度要更高,但在输入数据大小 $n$ 比较小时,算法 `B` 是要明显优于算法 `C` 的。对于以上情况,我们很难仅凭时间复杂度来判定算法效率高低。然而,即使存在这些问题,计算复杂度仍然是评判算法效率的最有效且常用的方法。 ## 函数渐近上界 @@ -538,9 +538,9 @@ $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得 对着代码,从上到下一行一行地计数即可。然而,**由于上述 $c \cdot f(n)$ 中的常数项 $c$ 可以取任意大小,因此操作数量 $T(n)$ 中的各种系数、常数项都可以被忽略**。根据此原则,可以总结出以下计数偷懒技巧: -1. **跳过数量与 $n$ 无关的操作。** 因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。 -2. **省略所有系数。** 例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。 -3. **循环嵌套时使用乘法。** 总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。 +1. **跳过数量与 $n$ 无关的操作**。因为他们都是 $T(n)$ 中的常数项,对时间复杂度不产生影响。 +2. **省略所有系数**。例如,循环 $2n$ 次、$5n + 1$ 次、……,都可以化简记为 $n$ 次,因为 $n$ 前面的系数对时间复杂度也不产生影响。 +3. **循环嵌套时使用乘法**。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。 根据以下示例,使用上述技巧前、后的统计结果分别为 @@ -1004,7 +1004,7 @@ $$ !!! tip - **数据大小 $n$ 是根据输入数据的类型来确定的。** 比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。 + **数据大小 $n$ 是根据输入数据的类型来确定的**。比如,在上述示例中,我们直接将 $n$ 看作输入数据大小;以下遍历数组示例中,数据大小 $n$ 为数组的长度。 === "Java" @@ -2308,7 +2308,7 @@ $$ ## 最差、最佳、平均时间复杂度 -**某些算法的时间复杂度不是恒定的,而是与输入数据的分布有关。** 举一个例子,输入一个长度为 $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/docs/chapter_data_structure/classification_of_data_structure.md b/docs/chapter_data_structure/classification_of_data_structure.md index d4397c5da..749542eb9 100644 --- a/docs/chapter_data_structure/classification_of_data_structure.md +++ b/docs/chapter_data_structure/classification_of_data_structure.md @@ -8,7 +8,7 @@ comments: true ## 逻辑结构:线性与非线性 -**「逻辑结构」反映了数据之间的逻辑关系。** 数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。 +**「逻辑结构」反映了数据之间的逻辑关系**。数组和链表的数据按照顺序依次排列,反映了数据间的线性关系;树从顶至底按层级排列,反映了祖先与后代之间的派生关系;图由结点和边组成,反映了复杂网络关系。 我们一般将逻辑结构分为「线性」和「非线性」两种。“线性”这个概念很直观,即表明数据在逻辑关系上是排成一条线的;而如果数据之间的逻辑关系是非线形的(例如是网状或树状的),那么就是非线性数据结构。 @@ -25,13 +25,13 @@ comments: true 若感到阅读困难,建议先看完下个章节「数组与链表」,再回过头来理解物理结构的含义。 -**「物理结构」反映了数据在计算机内存中的存储方式。** 从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储** 。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。 +**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储** 。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。 ![classification_phisical_structure](classification_of_data_structure.assets/classification_phisical_structure.png)

Fig. 连续空间存储与离散空间存储

-**所有数据结构都是基于数组、或链表、或两者组合实现的。** 例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。 +**所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。 - **基于数组可实现:** 栈、队列、堆、哈希表、矩阵、张量(维度 $\geq 3$ 的数组)等; - **基于链表可实现:** 栈、队列、堆、哈希表、树、图等; diff --git a/docs/chapter_data_structure/data_and_memory.md b/docs/chapter_data_structure/data_and_memory.md index c620032aa..3a11fdf36 100644 --- a/docs/chapter_data_structure/data_and_memory.md +++ b/docs/chapter_data_structure/data_and_memory.md @@ -128,12 +128,12 @@ comments: true 在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度更快,但容量较小(通常为 GB 级别)。 -**算法运行中,相关数据都被存储在内存中。** 下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。 +**算法运行中,相关数据都被存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。 -**系统通过「内存地址 Memory Location」来访问目标内存位置的数据。** 计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。 +**系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。 ![computer_memory_location](data_and_memory.assets/computer_memory_location.png)

Fig. 内存条、内存空间、内存地址

-**内存资源是设计数据结构与算法的重要考虑因素。** 内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。 +**内存资源是设计数据结构与算法的重要考虑因素**。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。 diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md new file mode 100644 index 000000000..def4cde5b --- /dev/null +++ b/docs/chapter_heap/heap.md @@ -0,0 +1,32 @@ +# 堆 + +「堆 Heap」是一种特殊的树状数据结构,并且是一颗「完全二叉树」。堆主要分为两种: + +- 「大顶堆 Max Heap」,任意父结点的值 > 其子结点的值,因此根结点的值最大; +- 「小顶堆 Min Heap」,任意父结点的值 < 其子结点的值,因此根结点的值最小; + +(图) + +!!! tip "" + + 大顶堆和小顶堆的定义、性质、操作本质上是一样的。区别只是大顶堆在求最大值,小顶堆在求最小值。在下文中,我们将统一用「大顶堆」来举例,「小顶堆」的用法与实现可以简单地将所有 $>$ ($<$) 替换为 $<$ ($>$) 即可。 + +## 堆常用操作 + +堆的初始化。 + +获取堆顶元素。 + +添加与删除元素。 + +## 堆的实现 + +在二叉树章节中,我们讲过二叉树的数组表示方法,并且提到完全二叉树非常适合用数组来表示,因此我们一般使用「数组」来存储「堆」。 + + + +## 堆常见应用 + +- 优先队列。 +- 堆排序。 +- 获取数据 Top K 大(小)元素。 diff --git a/docs/chapter_introduction/algorithms_are_everywhere.md b/docs/chapter_introduction/algorithms_are_everywhere.md index b80a525b0..5d80f9c92 100644 --- a/docs/chapter_introduction/algorithms_are_everywhere.md +++ b/docs/chapter_introduction/algorithms_are_everywhere.md @@ -6,13 +6,13 @@ comments: true 听到“算法”这个词,我们一般会联想到数学。但实际上,大多数算法并不包含复杂的数学,而更像是在考察基本逻辑,而这些逻辑在我们日常生活中处处可见。 -在正式介绍算法之前,我想告诉你一件有趣的事:**其实,你在过去已经学会了很多算法,并且已经习惯将它们应用到日常生活中。** 接下来,我将介绍两个具体例子来佐证。 +在正式介绍算法之前,我想告诉你一件有趣的事:**其实,你在过去已经学会了很多算法,并且已经习惯将它们应用到日常生活中**。接下来,我将介绍两个具体例子来佐证。 -**例一:拼积木。** 一套积木,除了有许多部件之外,还会附送详细的拼装说明书。我们按照说明书上一步步操作,即可拼出复杂的积木模型。 +**例一:拼积木**。一套积木,除了有许多部件之外,还会附送详细的拼装说明书。我们按照说明书上一步步操作,即可拼出复杂的积木模型。 如果从数据结构与算法的角度看,大大小小的「积木」就是数据结构,而「拼装说明书」上的一系列步骤就是算法。 -**例二:查字典。** 在字典中,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母表顺序排列的。假设需要在字典中查询任意一个拼音首字母为 $r$ 的字,一般我们会这样做: +**例二:查字典**。在字典中,每个汉字都有一个对应的拼音,而字典是按照拼音的英文字母表顺序排列的。假设需要在字典中查询任意一个拼音首字母为 $r$ 的字,一般我们会这样做: 1. 打开字典大致一半页数的位置,查看此页的首字母是什么(假设为 $m$ ); 2. 由于在英文字母表中 $r$ 在 $m$ 的后面,因此应排除字典前半部分,查找范围仅剩后半部分; diff --git a/docs/chapter_preface/about_the_book.md b/docs/chapter_preface/about_the_book.md index 54cd2844c..cb926b941 100644 --- a/docs/chapter_preface/about_the_book.md +++ b/docs/chapter_preface/about_the_book.md @@ -199,19 +199,19 @@ comments: true ??? abstract "默认折叠,可以跳过" - **以实践为主。** 我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。 + **以实践为主**。我们知道,学习英语期间光啃书本是远远不够的,需要多听、多说、多写,在实践中培养语感、积累经验。编程语言也是一门语言,因此学习方法也应是类似的,需要多看优秀代码、多敲键盘、多思考代码逻辑。 本书的理论部分占少量篇幅,主要分为两类:一是基础且必要的概念知识,以培养读者对于算法的感性认识;二是重要的分类、对比或总结,这是为了帮助你站在更高视角俯瞰各个知识点,形成连点成面的效果。 实践部分主要由示例和代码组成。代码配有简要注释,复杂示例会尽可能地使用视觉化的形式呈现。我强烈建议读者对照着代码自己敲一遍,如果时间有限,也至少逐行读、复制并运行一遍,配合着讲解将代码吃透。 - **视觉化学习。** 信息时代以来,视觉化的脚步从未停止。媒体形式经历了文字短信、图文 Email 、动图、短(长)视频、交互式 Web 、3D 游戏等演变过程,信息的视觉化程度越来越高、愈加符合人类感官、信息传播效率大大提升。科技界也在向视觉化迈进,iPhone 就是一个典型例子,其相对于传统手机是高度视觉化的,包含精心设计的字体、主题配色、交互动画等。 + **视觉化学习**。信息时代以来,视觉化的脚步从未停止。媒体形式经历了文字短信、图文 Email 、动图、短(长)视频、交互式 Web 、3D 游戏等演变过程,信息的视觉化程度越来越高、愈加符合人类感官、信息传播效率大大提升。科技界也在向视觉化迈进,iPhone 就是一个典型例子,其相对于传统手机是高度视觉化的,包含精心设计的字体、主题配色、交互动画等。 近两年,短视频成为最受欢迎的信息媒介,可以在短时间内将高密度的信息“灌”给我们,有着极其舒适的观看体验。阅读则不然,读者与书本之间天然存在一种“疏离感”,我们看书会累、会走神、会停下来想其他事、会划下喜欢的句子、会思考某一片段的含义,这种疏离感给了读者与书本之间对话的可能,拓宽了想象空间。 本书作为一本入门教材,希望可以保有书本的“慢节奏”,但也会避免与读者产生过多“疏离感”,而是努力将知识完整清晰地推送到你聪明的小脑袋瓜中。我将采用视觉化的方式(例如配图、动画),尽我可能清晰易懂地讲解复杂概念和抽象示例。 - **内容精简化。** 大多数的经典教科书,会把每个主题都讲的很透彻。虽然透彻性正是其获得读者青睐的原因,但对于想要快速入门的初学者来说,这些教材的实用性不足。本书会避免引入非必要的概念、名词、定义等,也避免展开不必要的理论分析,毕竟这不是一本真正意义上的教材,主要任务是尽快地带领读者入门。 + **内容精简化**。大多数的经典教科书,会把每个主题都讲的很透彻。虽然透彻性正是其获得读者青睐的原因,但对于想要快速入门的初学者来说,这些教材的实用性不足。本书会避免引入非必要的概念、名词、定义等,也避免展开不必要的理论分析,毕竟这不是一本真正意义上的教材,主要任务是尽快地带领读者入门。 引入一些生活案例或趣味内容,非常适合作为知识点的引子或者解释的补充,但当融入过多额外元素时,内容会稍显冗长,也许反而使读者容易迷失、抓不住重点,这也是本书需要避免的。 diff --git a/docs/chapter_preface/suggestions.md b/docs/chapter_preface/suggestions.md index c0377ee34..d4dbd24bc 100644 --- a/docs/chapter_preface/suggestions.md +++ b/docs/chapter_preface/suggestions.md @@ -54,10 +54,10 @@ git clone https://github.com/krahets/hello-algo.git ## 算法学习“三步走” -**第一阶段,算法入门,也正是本书的定位。** 熟悉各种数据结构的特点、用法,学习各种算法的工作原理、用途、效率等。 +**第一阶段,算法入门,也正是本书的定位**。熟悉各种数据结构的特点、用法,学习各种算法的工作原理、用途、效率等。 -**第二阶段,刷算法题。** 可以先从热门题单开刷,推荐 [剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode 热题 HOT 100](https://leetcode.cn/problem-list/2cktkvj/) ,先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,当做了三遍以上,往往就能牢记于心了。 +**第二阶段,刷算法题**。可以先从热门题单开刷,推荐 [剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode 热题 HOT 100](https://leetcode.cn/problem-list/2cktkvj/) ,先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,当做了三遍以上,往往就能牢记于心了。 -**第三阶段,搭建知识体系。** 在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,刷题方案在社区中可以找到一些讲解,在此不做赘述。 +**第三阶段,搭建知识体系**。在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,刷题方案在社区中可以找到一些讲解,在此不做赘述。 ![learning_route](suggestions.assets/learning_route.png) diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md index 6bc53fe5a..8827588ef 100644 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -470,11 +470,11 @@ $$ 二分查找效率很高,体现在: -- **二分查找时间复杂度低。** 对数阶在数据量很大时具有巨大优势,例如,当数据大小 $n = 2^{20}$ 时,线性查找需要 $2^{20} = 1048576$ 轮循环,而二分查找仅需要 $\log_2 2^{20} = 20$ 轮循环。 -- **二分查找不需要额外空间。** 相对于借助额外数据结构来实现查找的算法来说,其更加节约空间使用。 +- **二分查找时间复杂度低**。对数阶在数据量很大时具有巨大优势,例如,当数据大小 $n = 2^{20}$ 时,线性查找需要 $2^{20} = 1048576$ 轮循环,而二分查找仅需要 $\log_2 2^{20} = 20$ 轮循环。 +- **二分查找不需要额外空间**。相对于借助额外数据结构来实现查找的算法来说,其更加节约空间使用。 但并不意味着所有情况下都应使用二分查找,这是因为: -- **二分查找仅适用于有序数据。** 如果输入数据是无序的,为了使用二分查找而专门执行数据排序,那么是得不偿失的,因为排序算法的时间复杂度一般为 $O(n \log n)$ ,比线性查找和二分查找都更差。再例如,对于频繁插入元素的场景,为了保持数组的有序性,需要将元素插入到特定位置,时间复杂度为 $O(n)$ ,也是非常昂贵的。 -- **二分查找仅适用于数组。** 由于在二分查找中,访问索引是 ”非连续“ 的,因此链表或者基于链表实现的数据结构都无法使用。 -- **在小数据量下,线性查找的性能更好。** 在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,在数据量 $n$ 较小时,线性查找反而比二分查找更快。 +- **二分查找仅适用于有序数据**。如果输入数据是无序的,为了使用二分查找而专门执行数据排序,那么是得不偿失的,因为排序算法的时间复杂度一般为 $O(n \log n)$ ,比线性查找和二分查找都更差。再例如,对于频繁插入元素的场景,为了保持数组的有序性,需要将元素插入到特定位置,时间复杂度为 $O(n)$ ,也是非常昂贵的。 +- **二分查找仅适用于数组**。由于在二分查找中,访问索引是 ”非连续“ 的,因此链表或者基于链表实现的数据结构都无法使用。 +- **在小数据量下,线性查找的性能更好**。在线性查找中,每轮只需要 1 次判断操作;而在二分查找中,需要 1 次加法、1 次除法、1 ~ 3 次判断操作、1 次加法(减法),共 4 ~ 6 个单元操作;因此,在数据量 $n$ 较小时,线性查找反而比二分查找更快。 diff --git a/docs/chapter_searching/linear_search.md b/docs/chapter_searching/linear_search.md index 3d592962e..1c07027e6 100644 --- a/docs/chapter_searching/linear_search.md +++ b/docs/chapter_searching/linear_search.md @@ -246,6 +246,6 @@ comments: true ## 优点与缺点 -**线性查找的通用性极佳。** 由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。 +**线性查找的通用性极佳**。由于线性查找是依次访问元素的,即没有跳跃访问元素,因此数组或链表皆适用。 -**线性查找的时间复杂度太高。** 在数据量 $n$ 很大时,查找效率很低。 +**线性查找的时间复杂度太高**。在数据量 $n$ 很大时,查找效率很低。 diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index b7b607f27..ebc161caa 100644 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -383,7 +383,7 @@ comments: true ## 基准数优化 -**普通快速排序在某些输入下的时间效率变差。** 举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$ 、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 +**普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$ 、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。 为了尽量避免这种情况发生,我们可以优化一下基准数的选取策略。首先,在哨兵划分中,我们可以 **随机选取一个元素作为基准数** 。但如果运气很差,每次都选择到比较差的基准数,那么效率依然不好。 @@ -576,7 +576,7 @@ comments: true ## 尾递归优化 -**普通快速排序在某些输入下的空间效率变差。** 仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 +**普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 为了避免栈帧空间的累积,我们可以在每轮哨兵排序完成后,判断两个子数组的长度大小,仅递归排序较短的子数组。由于较短的子数组长度不会超过 $\frac{n}{2}$ ,因此这样做能保证递归深度不超过 $\log n$ ,即最差空间复杂度被优化至 $O(\log n)$ 。 diff --git a/docs/chapter_stack_and_queue/deque.md b/docs/chapter_stack_and_queue/deque.md index 72106360f..c178c0e0a 100644 --- a/docs/chapter_stack_and_queue/deque.md +++ b/docs/chapter_stack_and_queue/deque.md @@ -12,7 +12,7 @@ comments: true ## 双向队列常用操作 -双向队列的常用操作见下表,方法名需根据编程语言设定来具体确定。 +双向队列的常用操作见下表(方法命名以 Java 为例)。

Table. 双向队列的常用操作

@@ -38,25 +38,25 @@ comments: true ```java title="deque.java" /* 初始化双向队列 */ Deque deque = new LinkedList<>(); - + /* 元素入队 */ deque.offerLast(2); // 添加至队尾 deque.offerLast(5); deque.offerLast(4); deque.offerFirst(3); // 添加至队首 deque.offerFirst(1); - + /* 访问元素 */ int peekFirst = deque.peekFirst(); // 队首元素 int peekLast = deque.peekLast(); // 队尾元素 - + /* 元素出队 */ int pollFirst = deque.pollFirst(); // 队首元素出队 int pollLast = deque.pollLast(); // 队尾元素出队 - + /* 获取双向队列的长度 */ int size = deque.size(); - + /* 判断双向队列是否为空 */ boolean isEmpty = deque.isEmpty(); ``` @@ -66,25 +66,25 @@ comments: true ```cpp title="deque.cpp" /* 初始化双向队列 */ deque deque; - + /* 元素入队 */ deque.push_back(2); // 添加至队尾 deque.push_back(5); deque.push_back(4); deque.push_front(3); // 添加至队首 deque.push_front(1); - + /* 访问元素 */ int front = deque.front(); // 队首元素 int back = deque.back(); // 队尾元素 - + /* 元素出队 */ deque.pop_front(); // 队首元素出队 deque.pop_back(); // 队尾元素出队 - + /* 获取双向队列的长度 */ int size = deque.size(); - + /* 判断双向队列是否为空 */ bool empty = deque.empty(); ``` @@ -94,25 +94,25 @@ comments: true ```python title="deque.py" """ 初始化双向队列 """ duque = deque() - + """ 元素入队 """ duque.append(2) # 添加至队尾 duque.append(5) duque.append(4) duque.appendleft(3) # 添加至队首 duque.appendleft(1) - + """ 访问元素 """ front = duque[0] # 队首元素 rear = duque[-1] # 队尾元素 - + """ 元素出队 """ pop_front = duque.popleft() # 队首元素出队 pop_rear = duque.pop() # 队尾元素出队 - + """ 获取双向队列的长度 """ size = len(duque) - + """ 判断双向队列是否为空 """ is_empty = len(duque) == 0 ``` @@ -123,25 +123,25 @@ comments: true /* 初始化双向队列 */ // 在 Go 中,将 list 作为双向队列使用 deque := list.New() - + /* 元素入队 */ deque.PushBack(2) // 添加至队尾 deque.PushBack(5) deque.PushBack(4) deque.PushFront(3) // 添加至队首 deque.PushFront(1) - + /* 访问元素 */ front := deque.Front() // 队首元素 rear := deque.Back() // 队尾元素 - + /* 元素出队 */ deque.Remove(front) // 队首元素出队 deque.Remove(rear) // 队尾元素出队 - + /* 获取双向队列的长度 */ size := deque.Len() - + /* 判断双向队列是否为空 */ isEmpty := deque.Len() == 0 ``` @@ -149,19 +149,19 @@ comments: true === "JavaScript" ```js title="deque.js" - + ``` === "TypeScript" ```typescript title="deque.ts" - + ``` === "C" ```c title="deque.c" - + ``` === "C#" @@ -170,25 +170,25 @@ comments: true /* 初始化双向队列 */ // 在 C# 中,将链表 LinkedList 看作双向队列来使用 LinkedList deque = new LinkedList(); - + /* 元素入队 */ deque.AddLast(2); // 添加至队尾 deque.AddLast(5); deque.AddLast(4); deque.AddFirst(3); // 添加至队首 deque.AddFirst(1); - + /* 访问元素 */ int peekFirst = deque.First.Value; // 队首元素 int peekLast = deque.Last.Value; // 队尾元素 - + /* 元素出队 */ deque.RemoveFirst(); // 队首元素出队 deque.RemoveLast(); // 队尾元素出队 - + /* 获取双向队列的长度 */ int size = deque.Count; - + /* 判断双向队列是否为空 */ bool isEmpty = deque.Count == 0; ``` diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index cbf854f9c..8a964d3b7 100644 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -14,7 +14,7 @@ comments: true ## 队列常用操作 -队列的常用操作见下表,方法命名需根据编程语言的设定来具体确定。 +队列的常用操作见下表(方法命名以 Java 为例)。

Table. 队列的常用操作

@@ -37,23 +37,23 @@ comments: true ```java title="queue.java" /* 初始化队列 */ Queue queue = new LinkedList<>(); - + /* 元素入队 */ queue.offer(1); queue.offer(3); queue.offer(2); queue.offer(5); queue.offer(4); - + /* 访问队首元素 */ int peek = queue.peek(); - + /* 元素出队 */ int poll = queue.poll(); - + /* 获取队列的长度 */ int size = queue.size(); - + /* 判断队列是否为空 */ boolean isEmpty = queue.isEmpty(); ``` @@ -91,23 +91,23 @@ comments: true # 在 Python 中,我们一般将双向队列类 deque 看作队列使用 # 虽然 queue.Queue() 是纯正的队列类,但不太好用,因此不建议 que = collections.deque() - + """ 元素入队 """ que.append(1) que.append(3) que.append(2) que.append(5) que.append(4) - + """ 访问队首元素 """ front = que[0]; - + """ 元素出队 """ pop = que.popleft() - + """ 获取队列的长度 """ size = len(que) - + """ 判断队列是否为空 """ is_empty = len(que) == 0 ``` @@ -118,24 +118,24 @@ comments: true /* 初始化队列 */ // 在 Go 中,将 list 作为队列来使用 queue := list.New() - + /* 元素入队 */ queue.PushBack(1) queue.PushBack(3) queue.PushBack(2) queue.PushBack(5) queue.PushBack(4) - + /* 访问队首元素 */ peek := queue.Front() - + /* 元素出队 */ poll := queue.Front() queue.Remove(poll) - + /* 获取队列的长度 */ size := queue.Len() - + /* 判断队列是否为空 */ isEmpty := queue.Len() == 0 ``` @@ -146,24 +146,24 @@ comments: true /* 初始化队列 */ // JavaScript 没有内置的队列,可以把 Array 当作队列来使用 const queue = []; - + /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); - + /* 访问队首元素 */ const peek = queue[0]; - + /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) const poll = queue.shift(); - + /* 获取队列的长度 */ const size = queue.length; - + /* 判断队列是否为空 */ const empty = queue.length === 0; ``` @@ -174,24 +174,24 @@ comments: true /* 初始化队列 */ // TypeScript 没有内置的队列,可以把 Array 当作队列来使用 const queue: number[] = []; - + /* 元素入队 */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); - + /* 访问队首元素 */ const peek = queue[0]; - + /* 元素出队 */ // 底层是数组,因此 shift() 方法的时间复杂度为 O(n) const poll = queue.shift(); - + /* 获取队列的长度 */ const size = queue.length; - + /* 判断队列是否为空 */ const empty = queue.length === 0; ``` @@ -199,7 +199,7 @@ comments: true === "C" ```c title="queue.c" - + ``` === "C#" @@ -207,23 +207,23 @@ comments: true ```csharp title="queue.cs" /* 初始化队列 */ Queue queue = new(); - + /* 元素入队 */ queue.Enqueue(1); queue.Enqueue(3); queue.Enqueue(2); queue.Enqueue(5); queue.Enqueue(4); - + /* 访问队首元素 */ int peek = queue.Peek(); - + /* 元素出队 */ int poll = queue.Dequeue(); - + /* 获取队列的长度 */ int size = queue.Count(); - + /* 判断队列是否为空 */ bool isEmpty = queue.Count() == 0; ``` @@ -243,7 +243,7 @@ comments: true class LinkedListQueue { private ListNode front, rear; // 头结点 front ,尾结点 rear private int queSize = 0; - + public LinkedListQueue() { front = null; rear = null; @@ -296,7 +296,7 @@ comments: true private: ListNode *front, *rear; // 头结点 front ,尾结点 rear int queSize; - + public: LinkedListQueue() { front = nullptr; @@ -355,15 +355,15 @@ comments: true self.__front = None # 头结点 front self.__rear = None # 尾结点 rear self.__size = 0 - + """ 获取队列的长度 """ def size(self): return self.__size - + """ 判断队列是否为空 """ def is_empty(self): return not self.__front - + """ 入队 """ def push(self, num): # 尾结点后添加 num @@ -377,7 +377,7 @@ comments: true self.__rear.next = node self.__rear = node self.__size += 1 - + """ 出队 """ def poll(self): num = self.peek() @@ -385,7 +385,7 @@ comments: true self.__front = self.__front.next self.__size -= 1 return num - + """ 访问队首元素 """ def peek(self): if self.size() == 0: @@ -548,7 +548,7 @@ comments: true === "C" ```c title="linkedlist_queue.c" - + ``` === "C#" @@ -630,7 +630,7 @@ comments: true private int[] nums; // 用于存储队列元素的数组 private int front = 0; // 头指针,指向队首 private int rear = 0; // 尾指针,指向队尾 + 1 - + public ArrayQueue(int capacity) { // 初始化数组 nums = new int[capacity]; @@ -686,7 +686,7 @@ comments: true int cap; // 队列容量 int front = 0; // 头指针,指向队首 int rear = 0; // 尾指针,指向队尾 + 1 - + public: ArrayQueue(int capacity) { // 初始化数组 @@ -741,20 +741,20 @@ comments: true self.__nums = [0] * size # 用于存储队列元素的数组 self.__front = 0 # 头指针,指向队首 self.__rear = 0 # 尾指针,指向队尾 + 1 - + """ 获取队列的容量 """ def capacity(self): return len(self.__nums) - + """ 获取队列的长度 """ def size(self): # 由于将数组看作为环形,可能 rear < front ,因此需要取余数 return (self.capacity() + self.__rear - self.__front) % self.capacity() - + """ 判断队列是否为空 """ def is_empty(self): return (self.__rear - self.__front) == 0 - + """ 入队 """ def push(self, val): if self.size() == self.capacity(): @@ -764,21 +764,21 @@ comments: true self.__nums[self.__rear] = val # 尾指针向后移动一位,越过尾部后返回到数组头部 self.__rear = (self.__rear + 1) % self.capacity() - + """ 出队 """ def poll(self): num = self.peek() # 队头指针向后移动一位,若越过尾部则返回到数组头部 self.__front = (self.__front + 1) % self.capacity() return num - + """ 访问队首元素 """ def peek(self): if self.is_empty(): print("队列为空") return False return self.__nums[self.__front] - + """ 返回列表用于打印 """ def to_list(self): res = [0] * self.size() @@ -950,7 +950,7 @@ comments: true === "C" ```c title="array_queue.c" - + ``` === "C#" @@ -1017,5 +1017,5 @@ comments: true ## 队列典型应用 -- **淘宝订单。** 购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 -- **各种待办事项。** 例如打印机的任务队列、餐厅的出餐队列等等。 +- **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 +- **各种待办事项**。例如打印机的任务队列、餐厅的出餐队列等等。 diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index 825cda9f9..985a4cf66 100644 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -16,7 +16,7 @@ comments: true ## 栈常用操作 -栈的常用操作见下表,方法名需根据编程语言设定来具体确定。 +栈的常用操作见下表(方法命名以 Java 为例)。

Table. 栈的常用操作

@@ -40,23 +40,23 @@ comments: true /* 初始化栈 */ // 在 Java 中,推荐将 LinkedList 当作栈来使用 LinkedList stack = new LinkedList<>(); - + /* 元素入栈 */ stack.addLast(1); stack.addLast(3); stack.addLast(2); stack.addLast(5); stack.addLast(4); - + /* 访问栈顶元素 */ int peek = stack.peekLast(); - + /* 元素出栈 */ int pop = stack.removeLast(); - + /* 获取栈的长度 */ int size = stack.size(); - + /* 判断是否为空 */ boolean isEmpty = stack.isEmpty(); ``` @@ -66,23 +66,23 @@ comments: true ```cpp title="stack.cpp" /* 初始化栈 */ stack stack; - + /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - + /* 访问栈顶元素 */ int top = stack.top(); - + /* 元素出栈 */ stack.pop(); - + /* 获取栈的长度 */ int size = stack.size(); - + /* 判断是否为空 */ bool empty = stack.empty(); ``` @@ -93,23 +93,23 @@ comments: true """ 初始化栈 """ # Python 没有内置的栈类,可以把 List 当作栈来使用 stack = [] - + """ 元素入栈 """ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) - + """ 访问栈顶元素 """ peek = stack[-1] - + """ 元素出栈 """ pop = stack.pop() - + """ 获取栈的长度 """ size = len(stack) - + """ 判断是否为空 """ is_empty = len(stack) == 0 ``` @@ -120,24 +120,24 @@ comments: true /* 初始化栈 */ // 在 Go 中,推荐将 Slice 当作栈来使用 var stack []int - + /* 元素入栈 */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) - + /* 访问栈顶元素 */ peek := stack[len(stack)-1] - + /* 元素出栈 */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] - + /* 获取栈的长度 */ size := len(stack) - + /* 判断是否为空 */ isEmpty := len(stack) == 0 ``` @@ -148,23 +148,23 @@ comments: true /* 初始化栈 */ // Javascript 没有内置的栈类,可以把 Array 当作栈来使用 const stack = []; - + /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - + /* 访问栈顶元素 */ const peek = stack[stack.length-1]; - + /* 元素出栈 */ const pop = stack.pop(); - + /* 获取栈的长度 */ const size = stack.length; - + /* 判断是否为空 */ const is_empty = stack.length === 0; ``` @@ -175,23 +175,23 @@ comments: true /* 初始化栈 */ // Typescript 没有内置的栈类,可以把 Array 当作栈来使用 const stack: number[] = []; - + /* 元素入栈 */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - + /* 访问栈顶元素 */ const peek = stack[stack.length - 1]; - + /* 元素出栈 */ const pop = stack.pop(); - + /* 获取栈的长度 */ const size = stack.length; - + /* 判断是否为空 */ const is_empty = stack.length === 0; ``` @@ -199,7 +199,7 @@ comments: true === "C" ```c title="stack.c" - + ``` === "C#" @@ -207,23 +207,23 @@ comments: true ```csharp title="stack.cs" /* 初始化栈 */ Stack stack = new (); - + /* 元素入栈 */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); - + /* 访问栈顶元素 */ int peek = stack.Peek(); - + /* 元素出栈 */ int pop = stack.Pop(); - + /* 获取栈的长度 */ int size = stack.Count(); - + /* 判断是否为空 */ bool isEmpty = stack.Count()==0; ``` @@ -291,7 +291,7 @@ comments: true private: ListNode* stackTop; // 将头结点作为栈顶 int stkSize; // 栈的长度 - + public: LinkedListStack() { stackTop = nullptr; @@ -338,29 +338,29 @@ comments: true def __init__(self): self.__peek = None self.__size = 0 - + """ 获取栈的长度 """ def size(self): return self.__size - + """ 判断栈是否为空 """ def is_empty(self): return not self.__peek - + """ 入栈 """ def push(self, val): node = ListNode(val) node.next = self.__peek self.__peek = node self.__size += 1 - + """ 出栈 """ def pop(self): num = self.peek() self.__peek = self.__peek.next self.__size -= 1 return num - + """ 访问栈顶元素 """ def peek(self): # 判空处理 @@ -420,21 +420,21 @@ comments: true class LinkedListStack { #stackPeek; // 将头结点作为栈顶 #stkSize = 0; // 栈的长度 - + constructor() { this.#stackPeek = null; } - + /* 获取栈的长度 */ get size() { return this.#stkSize; } - + /* 判断栈是否为空 */ isEmpty() { return this.size == 0; } - + /* 入栈 */ push(num) { const node = new ListNode(num); @@ -442,7 +442,7 @@ comments: true this.#stackPeek = node; this.#stkSize++; } - + /* 出栈 */ pop() { const num = this.peek(); @@ -453,7 +453,7 @@ comments: true this.#stkSize--; return num; } - + /* 访问栈顶元素 */ peek() { if (!this.#stackPeek) { @@ -461,7 +461,7 @@ comments: true } return this.#stackPeek.val; } - + /* 将链表转化为 Array 并返回 */ toArray() { let node = this.#stackPeek; @@ -482,21 +482,21 @@ comments: true class LinkedListStack { private stackPeek: ListNode | null; // 将头结点作为栈顶 private stkSize: number = 0; // 栈的长度 - + constructor() { this.stackPeek = null; } - + /* 获取栈的长度 */ get size(): number { return this.stkSize; } - + /* 判断栈是否为空 */ isEmpty(): boolean { return this.size == 0; } - + /* 入栈 */ push(num: number): void { const node = new ListNode(num); @@ -504,7 +504,7 @@ comments: true this.stackPeek = node; this.stkSize++; } - + /* 出栈 */ pop(): number { const num = this.peek(); @@ -515,7 +515,7 @@ comments: true this.stkSize--; return num; } - + /* 访问栈顶元素 */ peek(): number { if (!this.stackPeek) { @@ -523,7 +523,7 @@ comments: true } return this.stackPeek.val; } - + /* 将链表转化为 Array 并返回 */ toArray(): number[] { let node = this.stackPeek; @@ -540,7 +540,7 @@ comments: true === "C" ```c title="linkedlist_stack.c" - + ``` === "C#" @@ -676,24 +676,24 @@ comments: true class ArrayStack: def __init__(self): self.__stack = [] - + """ 获取栈的长度 """ def size(self): return len(self.__stack) - + """ 判断栈是否为空 """ def is_empty(self): return self.__stack == [] - + """ 入栈 """ def push(self, item): self.__stack.append(item) - + """ 出栈 """ def pop(self): assert not self.is_empty(), "栈为空" return self.__stack.pop() - + """ 访问栈顶元素 """ def peek(self): assert not self.is_empty(), "栈为空" @@ -821,7 +821,7 @@ comments: true === "C" ```c title="array_stack.c" - + ``` === "C#" @@ -876,5 +876,5 @@ comments: true ## 栈典型应用 -- **浏览器中的后退与前进、软件中的撤销与反撤销。** 每当我们打开新的网页,浏览器就讲上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 -- **程序内存管理。** 每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。 +- **浏览器中的后退与前进、软件中的撤销与反撤销**。每当我们打开新的网页,浏览器就讲上一个网页执行入栈,这样我们就可以通过「后退」操作来回到上一页面,后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么则需要两个栈来配合实现。 +- **程序内存管理**。每当调用函数时,系统就会在栈顶添加一个栈帧,用来记录函数的上下文信息。在递归函数中,向下递推会不断执行入栈,向上回溯阶段时出栈。 diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index d969c66d7..fe08828c1 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -253,7 +253,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ## AVL 树旋转 -AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡。** 换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 +AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影响二叉树中序遍历序列的前提下,使失衡结点重新恢复平衡**。换言之,旋转操作既可以使树保持为「二叉搜索树」,也可以使树重新恢复为「平衡二叉树」。 我们将平衡因子的绝对值 $> 1$ 的结点称为「失衡结点」。根据结点的失衡情况,旋转操作分为 **右旋、左旋、先右旋后左旋、先左旋后右旋**,接下来我们来一起来看看它们是如何操作的。 diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index cc7c2f51c..60a5598bd 100644 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -430,15 +430,15 @@ comments: true 与插入结点一样,我们需要在删除操作后维持二叉搜索树的“左子树 < 根结点 < 右子树”的性质。首先,我们需要在二叉树中执行查找操作,获取待删除结点。接下来,根据待删除结点的子结点数量,删除操作需要分为三种情况: -**待删除结点的子结点数量 $= 0$ 。** 表明待删除结点是叶结点,直接删除即可。 +**待删除结点的子结点数量 $= 0$ **。表明待删除结点是叶结点,直接删除即可。 ![bst_remove_case1](binary_search_tree.assets/bst_remove_case1.png) -**待删除结点的子结点数量 $= 1$ 。** 将待删除结点替换为其子结点。 +**待删除结点的子结点数量 $= 1$ **。将待删除结点替换为其子结点。 ![bst_remove_case2](binary_search_tree.assets/bst_remove_case2.png) -**待删除结点的子结点数量 $= 2$ 。** 删除操作分为三步: +**待删除结点的子结点数量 $= 2$ **。删除操作分为三步: 1. 找到待删除结点在 **中序遍历序列** 中的下一个结点,记为 `nex` ; 2. 在树中递归删除结点 `nex` ; diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 3e3222222..7977ab6a2 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -137,7 +137,7 @@ comments: true ## 二叉树基本操作 -**初始化二叉树。** 与链表类似,先初始化结点,再构建引用指向(即指针)。 +**初始化二叉树**。与链表类似,先初始化结点,再构建引用指向(即指针)。 === "Java" @@ -263,7 +263,7 @@ comments: true n2.right = n5; ``` -**插入与删除结点。** 与链表类似,插入与删除结点都可以通过修改指针实现。 +**插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。 ![binary_tree_add_remove](binary_tree.assets/binary_tree_add_remove.png) @@ -497,7 +497,7 @@ comments: true ![array_representation_with_empty](binary_tree.assets/array_representation_with_empty.png) -回顾「完全二叉树」的满足条件,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。“便于使用数组表示”也是完全二叉树受欢迎的原因之一。 +回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。 ![array_representation_complete_binary_tree](binary_tree.assets/array_representation_complete_binary_tree.png) From f49c6740299079d60196c106c59c5d05b72d3796 Mon Sep 17 00:00:00 2001 From: nuomi1 Date: Thu, 5 Jan 2023 21:16:05 +0800 Subject: [PATCH 04/21] feat: add Swift codes for array article --- codes/swift/Package.swift | 2 + .../chapter_array_and_linkedlist/array.swift | 103 ++++++++++++++++++ docs/chapter_array_and_linkedlist/array.md | 91 ++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 codes/swift/chapter_array_and_linkedlist/array.swift diff --git a/codes/swift/Package.swift b/codes/swift/Package.swift index a292d9fd3..d9368354b 100644 --- a/codes/swift/Package.swift +++ b/codes/swift/Package.swift @@ -9,6 +9,7 @@ let package = Package( .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), .executable(name: "space_complexity", targets: ["space_complexity"]), .executable(name: "leetcode_two_sum", targets: ["leetcode_two_sum"]), + .executable(name: "array", targets: ["array"]), ], targets: [ .target(name: "utils", path: "utils"), @@ -16,5 +17,6 @@ let package = Package( .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), .executableTarget(name: "leetcode_two_sum", path: "chapter_computational_complexity", sources: ["leetcode_two_sum.swift"]), + .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), ] ) diff --git a/codes/swift/chapter_array_and_linkedlist/array.swift b/codes/swift/chapter_array_and_linkedlist/array.swift new file mode 100644 index 000000000..c0c7ab9b9 --- /dev/null +++ b/codes/swift/chapter_array_and_linkedlist/array.swift @@ -0,0 +1,103 @@ +/** + * File: array.swift + * Created Time: 2023-01-05 + * Author: nuomi1 (nuomi1@qq.com) + */ + +// 随机返回一个数组元素 +func randomAccess(nums: [Int]) -> Int { + // 在区间 [0, nums.count) 中随机抽取一个数字 + let randomIndex = nums.indices.randomElement()! + // 获取并返回随机元素 + let randomNum = nums[randomIndex] + return randomNum +} + +// 扩展数组长度 +func extend(nums: [Int], enlarge: Int) -> [Int] { + // 初始化一个扩展长度后的数组 + var res = Array(repeating: 0, count: nums.count + enlarge) + // 将原数组中的所有元素复制到新数组 + for i in nums.indices { + res[i] = nums[i] + } + // 返回扩展后的新数组 + return res +} + +// 在数组的索引 index 处插入元素 num +func insert(nums: inout [Int], num: Int, index: Int) { + // 把索引 index 以及之后的所有元素向后移动一位 + for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { + nums[i] = nums[i - 1] + } + // 将 num 赋给 index 处元素 + nums[index] = num +} + +// 删除索引 index 处元素 +func remove(nums: inout [Int], index: Int) { + let count = nums.count + // 把索引 index 之后的所有元素向前移动一位 + for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) { + nums[i] = nums[i + 1] + } +} + +// 遍历数组 +func traverse(nums: [Int]) { + var count = 0 + // 通过索引遍历数组 + for _ in nums.indices { + count += 1 + } + // 直接遍历数组 + for _ in nums { + count += 1 + } +} + +// 在数组中查找指定元素 +func find(nums: [Int], target: Int) -> Int { + for i in nums.indices { + if nums[i] == target { + return i + } + } + return -1 +} + +@main +enum _Array { + // Driver Code + static func main() { + // 初始化数组 + let arr = Array(repeating: 0, count: 5) + print("数组 arr = \(arr)") + var nums = [1, 3, 2, 5, 4] + print("数组 nums = \(nums)") + + // 随机访问 + let randomNum = randomAccess(nums: nums) + print("在 nums 中获取随机元素 \(randomNum)") + + // 长度扩展 + nums = extend(nums: nums, enlarge: 3) + print("将数组长度扩展至 8 ,得到 nums = \(nums)") + + // 插入元素 + insert(nums: &nums, num: 6, index: 3) + print("在索引 3 处插入数字 6 ,得到 nums = \(nums)") + + // 删除元素 + remove(nums: &nums, index: 2) + print("删除索引 2 处的元素,得到 nums = \(nums)") + + // 遍历数组 + traverse(nums: nums) + + // 查找元素 + let index = find(nums: nums, target: 3) + print("在 nums 中查找元素 3 ,得到索引 = \(index)") + } +} diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 648a9c753..5e814ea24 100644 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -81,6 +81,14 @@ comments: true int[] nums = { 1, 3, 2, 5, 4 }; ``` +=== "Swift" + + ```swift title="array.swift" + // 初始化数组 + let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] + let nums = [1, 3, 2, 5, 4] + ``` + ## 数组优点 **在数组中访问元素非常高效。** 这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。 @@ -193,6 +201,19 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` +=== "Swift" + + ```swift title="array.swift" + // 随机返回一个数组元素 + func randomAccess(nums: [Int]) -> Int { + // 在区间 [0, nums.count) 中随机抽取一个数字 + let randomIndex = nums.indices.randomElement()! + // 获取并返回随机元素 + let randomNum = nums[randomIndex] + return randomNum + } + ``` + ## 数组缺点 **数组在初始化后长度不可变。** 由于系统无法保证数组之后的内存空间是可用的,因此数组长度无法扩展。而若希望扩容数组,则需新建一个数组,然后把原数组元素依次拷贝到新数组,在数组很大的情况下,这是非常耗时的。 @@ -317,6 +338,22 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` +=== "Swift" + + ```swift title="array.swift" + // 扩展数组长度 + func extend(nums: [Int], enlarge: Int) -> [Int] { + // 初始化一个扩展长度后的数组 + var res = Array(repeating: 0, count: nums.count + enlarge) + // 将原数组中的所有元素复制到新数组 + for i in nums.indices { + res[i] = nums[i] + } + // 返回扩展后的新数组 + return res + } + ``` + **数组中插入或删除元素效率低下。** 假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点: - **时间复杂度高:** 数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。 @@ -486,6 +523,29 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` +=== "Swift" + + ```swift title="array.swift" + // 在数组的索引 index 处插入元素 num + func insert(nums: inout [Int], num: Int, index: Int) { + // 把索引 index 以及之后的所有元素向后移动一位 + for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { + nums[i] = nums[i - 1] + } + // 将 num 赋给 index 处元素 + nums[index] = num + } + + // 删除索引 index 处元素 + func remove(nums: inout [Int], index: Int) { + let count = nums.count + // 把索引 index 之后的所有元素向前移动一位 + for i in sequence(first: index, next: { $0 < count - 1 - 1 ? $0 + 1 : nil }) { + nums[i] = nums[i + 1] + } + } + ``` + ## 数组常用操作 **数组遍历。** 以下介绍两种常用的遍历方法。 @@ -611,6 +671,23 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` +=== "Swift" + + ```swift title="array.swift" + // 遍历数组 + func traverse(nums: [Int]) { + var count = 0 + // 通过索引遍历数组 + for _ in nums.indices { + count += 1 + } + // 直接遍历数组 + for _ in nums { + count += 1 + } + } + ``` + **数组查找。** 通过遍历数组,查找数组内的指定元素,并输出对应索引。 === "Java" @@ -713,6 +790,20 @@ elementAddr = firtstElementAddr + elementLength * elementIndex } ``` +=== "Swift" + + ```swift title="array.swift" + // 在数组中查找指定元素 + func find(nums: [Int], target: Int) -> Int { + for i in nums.indices { + if nums[i] == target { + return i + } + } + return -1 + } + ``` + ## 数组典型应用 **随机访问。** 如果我们想要随机抽取一些样本,那么可以用数组存储,并生成一个随机序列,根据索引实现样本的随机抽取。 From dcc3b2e35b1d2fd8239e9904fea81e40ea83ad45 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sun, 8 Jan 2023 19:03:22 +0800 Subject: [PATCH 05/21] Optimize arrToTree function in java, cpp, py, go, js, ts. --- .gitignore | 4 +- codes/cpp/chapter_tree/binary_tree_bfs.cpp | 3 +- codes/cpp/chapter_tree/binary_tree_dfs.cpp | 3 +- codes/cpp/include/TreeNode.hpp | 15 +-- codes/csharp/chapter_tree/binary_tree_bfs.cs | 3 +- codes/csharp/chapter_tree/binary_tree_dfs.cs | 3 +- codes/csharp/include/TreeNode.cs | 10 +- codes/go/chapter_tree/binary_tree_bfs_test.go | 6 +- codes/go/chapter_tree/binary_tree_dfs_test.go | 10 +- codes/go/pkg/tree_node.go | 10 +- codes/go/pkg/tree_node_test.go | 2 +- codes/java/chapter_heap/my_heap.java | 115 ++++++++++++++++++ codes/java/chapter_tree/binary_tree_bfs.java | 3 +- codes/java/chapter_tree/binary_tree_dfs.java | 3 +- codes/java/include/PrintUtil.java | 8 ++ codes/java/include/TreeNode.java | 24 +--- .../chapter_tree/binary_tree_bfs.js | 4 +- .../chapter_tree/binary_tree_dfs.js | 3 +- codes/javascript/include/TreeNode.js | 15 +-- .../python/chapter_tree/binary_search_tree.py | 2 +- codes/python/chapter_tree/binary_tree.py | 2 +- codes/python/chapter_tree/binary_tree_bfs.py | 2 +- codes/python/chapter_tree/binary_tree_dfs.py | 2 +- codes/python/include/binary_tree.py | 36 ++---- .../chapter_tree/binary_tree_bfs.ts | 2 +- .../chapter_tree/binary_tree_dfs.ts | 2 +- codes/typescript/module/TreeNode.ts | 8 +- docs/chapter_heap/heap.md | 36 ++++-- .../binary_tree_add_remove.png | Bin 77839 -> 79400 bytes 29 files changed, 222 insertions(+), 114 deletions(-) create mode 100644 codes/java/chapter_heap/my_heap.java diff --git a/.gitignore b/.gitignore index 3ff305496..e93873c95 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,5 @@ docs/overrides/ # python files __pycache__ -# iml -hello-algo.iml +# in-progress articles +docs/chapter_heap diff --git a/codes/cpp/chapter_tree/binary_tree_bfs.cpp b/codes/cpp/chapter_tree/binary_tree_bfs.cpp index 236f62449..ffc2e372c 100644 --- a/codes/cpp/chapter_tree/binary_tree_bfs.cpp +++ b/codes/cpp/chapter_tree/binary_tree_bfs.cpp @@ -30,8 +30,7 @@ vector hierOrder(TreeNode* root) { int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode* root = vecToTree(vector - { 1, 2, 3, 4, 5, 6, 7, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX }); + TreeNode* root = vecToTree(vector { 1, 2, 3, 4, 5, 6, 7 }); cout << endl << "初始化二叉树\n" << endl; PrintUtil::printTree(root); diff --git a/codes/cpp/chapter_tree/binary_tree_dfs.cpp b/codes/cpp/chapter_tree/binary_tree_dfs.cpp index 51287b736..5ed5b78fe 100644 --- a/codes/cpp/chapter_tree/binary_tree_dfs.cpp +++ b/codes/cpp/chapter_tree/binary_tree_dfs.cpp @@ -41,8 +41,7 @@ void postOrder(TreeNode* root) { int main() { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode* root = vecToTree(vector - { 1, 2, 3, 4, 5, 6, 7, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX}); + TreeNode* root = vecToTree(vector { 1, 2, 3, 4, 5, 6, 7 }); cout << endl << "初始化二叉树\n" << endl; PrintUtil::printTree(root); diff --git a/codes/cpp/include/TreeNode.hpp b/codes/cpp/include/TreeNode.hpp index ab54c7746..82f2ce981 100644 --- a/codes/cpp/include/TreeNode.hpp +++ b/codes/cpp/include/TreeNode.hpp @@ -27,23 +27,24 @@ struct TreeNode { * @return TreeNode* */ TreeNode *vecToTree(vector list) { - if (list.empty()) { + if (list.empty()) return nullptr; - } auto *root = new TreeNode(list[0]); queue que; - size_t n = list.size(), index = 1; - while (index < n) { + que.emplace(root); + size_t n = list.size(), index = 0; + while (!que.empty()) { auto node = que.front(); que.pop(); - + if (++index >= n) break; if (index < n) { - node->left = new TreeNode(list[index++]); + node->left = new TreeNode(list[index]); que.emplace(node->left); } + if (++index >= n) break; if (index < n) { - node->right = new TreeNode(list[index++]); + node->right = new TreeNode(list[index]); que.emplace(node->right); } } diff --git a/codes/csharp/chapter_tree/binary_tree_bfs.cs b/codes/csharp/chapter_tree/binary_tree_bfs.cs index 31f707246..f0c914acb 100644 --- a/codes/csharp/chapter_tree/binary_tree_bfs.cs +++ b/codes/csharp/chapter_tree/binary_tree_bfs.cs @@ -41,8 +41,7 @@ namespace hello_algo.chapter_tree { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode? root = TreeNode.ArrToTree(new int?[] { - 1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null}); + TreeNode? root = TreeNode.ArrToTree(new int?[] { 1, 2, 3, 4, 5, 6, 7 }); Console.WriteLine("\n初始化二叉树\n"); PrintUtil.PrintTree(root); diff --git a/codes/csharp/chapter_tree/binary_tree_dfs.cs b/codes/csharp/chapter_tree/binary_tree_dfs.cs index 6e38ebe4c..0f89cb3b2 100644 --- a/codes/csharp/chapter_tree/binary_tree_dfs.cs +++ b/codes/csharp/chapter_tree/binary_tree_dfs.cs @@ -57,8 +57,7 @@ namespace hello_algo.chapter_tree { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode? root = TreeNode.ArrToTree(new int?[] { - 1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null}); + TreeNode? root = TreeNode.ArrToTree(new int?[] { 1, 2, 3, 4, 5, 6, 7 }); Console.WriteLine("\n初始化二叉树\n"); PrintUtil.PrintTree(root); diff --git a/codes/csharp/include/TreeNode.cs b/codes/csharp/include/TreeNode.cs index e5cacd59e..9cd0ac92c 100644 --- a/codes/csharp/include/TreeNode.cs +++ b/codes/csharp/include/TreeNode.cs @@ -19,7 +19,7 @@ namespace hello_algo.include } /** - * Generate a binary tree with an array + * Generate a binary tree given an array * @param arr * @return */ @@ -31,22 +31,22 @@ namespace hello_algo.include TreeNode root = new TreeNode((int) arr[0]); Queue queue = new Queue(); queue.Enqueue(root); - int i = 1; - while (queue.Count!=0) + int i = 0; + while (queue.Count != 0) { TreeNode node = queue.Dequeue(); + if (++i >= arr.Length) break; if (arr[i] != null) { node.left = new TreeNode((int) arr[i]); queue.Enqueue(node.left); } - i++; + if (++i >= arr.Length) break; if (arr[i] != null) { node.right = new TreeNode((int) arr[i]); queue.Enqueue(node.right); } - i++; } return root; } diff --git a/codes/go/chapter_tree/binary_tree_bfs_test.go b/codes/go/chapter_tree/binary_tree_bfs_test.go index 8d5c0aaff..d32dbdca9 100644 --- a/codes/go/chapter_tree/binary_tree_bfs_test.go +++ b/codes/go/chapter_tree/binary_tree_bfs_test.go @@ -14,11 +14,11 @@ import ( func TestLevelOrder(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - root := ArrayToTree([]int{1, 2, 3, 4, 5, 6, 7}) - fmt.Println("初始化二叉树: ") + root := ArrToTree([]int{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二叉树: ") PrintTree(root) // 层序遍历 nums := levelOrder(root) - fmt.Println("层序遍历的结点打印序列 =", nums) + fmt.Println("\n层序遍历的结点打印序列 =", nums) } diff --git a/codes/go/chapter_tree/binary_tree_dfs_test.go b/codes/go/chapter_tree/binary_tree_dfs_test.go index 67a3e1f3a..b0db8086c 100644 --- a/codes/go/chapter_tree/binary_tree_dfs_test.go +++ b/codes/go/chapter_tree/binary_tree_dfs_test.go @@ -14,22 +14,22 @@ import ( func TestPreInPostOrderTraversal(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - root := ArrayToTree([]int{1, 2, 3, 4, 5, 6, 7}) - fmt.Println("初始化二叉树: ") + root := ArrToTree([]int{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n初始化二叉树: ") PrintTree(root) // 前序遍历 nums = nil preOrder(root) - fmt.Println("前序遍历的结点打印序列 =", nums) + fmt.Println("\n前序遍历的结点打印序列 =", nums) // 中序遍历 nums = nil inOrder(root) - fmt.Println("中序遍历的结点打印序列 =", nums) + fmt.Println("\n中序遍历的结点打印序列 =", nums) // 后序遍历 nums = nil postOrder(root) - fmt.Println("后序遍历的结点打印序列 =", nums) + fmt.Println("\n后序遍历的结点打印序列 =", nums) } diff --git a/codes/go/pkg/tree_node.go b/codes/go/pkg/tree_node.go index 2511a7655..b1e630e67 100644 --- a/codes/go/pkg/tree_node.go +++ b/codes/go/pkg/tree_node.go @@ -22,8 +22,8 @@ func NewTreeNode(v int) *TreeNode { } } -// ArrayToTree Generate a binary tree with an array -func ArrayToTree(arr []int) *TreeNode { +// ArrToTree Generate a binary tree given an array +func ArrToTree(arr []int) *TreeNode { if len(arr) <= 0 { return nil } @@ -31,19 +31,19 @@ func ArrayToTree(arr []int) *TreeNode { // Let container.list as queue queue := list.New() queue.PushBack(root) - i := 1 + i := 0 for queue.Len() > 0 { // poll node := queue.Remove(queue.Front()).(*TreeNode) + i++ if i < len(arr) { node.Left = NewTreeNode(arr[i]) queue.PushBack(node.Left) - i++ } + i++ if i < len(arr) { node.Right = NewTreeNode(arr[i]) queue.PushBack(node.Right) - i++ } } return root diff --git a/codes/go/pkg/tree_node_test.go b/codes/go/pkg/tree_node_test.go index b4c8e0773..bb1885ee1 100644 --- a/codes/go/pkg/tree_node_test.go +++ b/codes/go/pkg/tree_node_test.go @@ -11,7 +11,7 @@ import ( func TestTreeNode(t *testing.T) { arr := []int{2, 3, 5, 6, 7} - node := ArrayToTree(arr) + node := ArrToTree(arr) // print tree PrintTree(node) diff --git a/codes/java/chapter_heap/my_heap.java b/codes/java/chapter_heap/my_heap.java new file mode 100644 index 000000000..da2dffc0a --- /dev/null +++ b/codes/java/chapter_heap/my_heap.java @@ -0,0 +1,115 @@ +/** + * File: my_heap.java + * Created Time: 2023-01-07 + * Author: Krahets (krahets@163.com) + */ + +package chapter_heap; + +import include.*; +import java.util.*; + +class MaxHeap { + private List heap; + + public MaxHeap() { + heap = new ArrayList<>(); + } + + public MaxHeap(List nums) { + // 将元素拷贝至堆中 + heap = new ArrayList<>(nums); + // 堆化除叶结点外的其他所有结点 + for (int i = parent(size() - 1); i >= 0; i--) { + heapify(i); + } + } + + /* 获取左子结点 */ + private int left(int i) { + return 2 * i + 1; + } + + /* 获取右子结点 */ + private int right(int i) { + return 2 * i + 2; + } + + /* 获取父结点 */ + private int parent(int i) { + return (i - 1) / 2; + } + + /* 交换元素 */ + private void swap(int i, int j) { + int tmp = heap.get(i); + heap.set(i, j); + heap.set(j, tmp); + } + + public int size() { + return heap.size(); + } + + public boolean isEmpty() { + return size() == 0; + } + + /* 获取堆顶元素 */ + public int peek() { + return heap.get(0); + } + + /* 元素入堆 */ + public void push(int val) { + heap.add(val); + + // 从底至顶堆化 + int i = size(); + while (true) { + int p = parent(i); + if (p < 0 || heap.get(i) > heap.get(p)) + break; + swap(i, p); + i = p; + } + } + + /* 元素出堆 */ + public int poll() { + // 判空处理 + if (isEmpty()) + throw new EmptyStackException(); + // 交换根结点与右下角(即最后一个)结点 + swap(0, size() - 1); + // 删除结点 + int val = heap.remove(size() - 1); + // 从顶至底堆化 + heapify(0); + // 返回堆顶元素 + return val; + } + + /* 从结点 i 开始,从顶至底堆化 */ + private void heapify(int i) { + while (true) { + // 判断结点 i, l, r 中的最大结点,记为 ma ; + int l = left(i), r = right(i), ma = i; + if (heap.get(l) > heap.get(ma)) ma = l; + if (heap.get(r) > heap.get(ma)) ma = r; + // 若结点 i 最大,则无需继续堆化,跳出 + if (ma == i) break; + // 交换结点 i 与结点 max + swap(i, ma); + // 循环向下堆化 + i = ma; + } + } +} + + +public class my_heap { + public static void main(String[] args) { + + } +} diff --git a/codes/java/chapter_tree/binary_tree_bfs.java b/codes/java/chapter_tree/binary_tree_bfs.java index c000eacea..450311d08 100644 --- a/codes/java/chapter_tree/binary_tree_bfs.java +++ b/codes/java/chapter_tree/binary_tree_bfs.java @@ -30,8 +30,7 @@ public class binary_tree_bfs { public static void main(String[] args) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode root = TreeNode.arrToTree(new Integer[] { - 1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null }); + TreeNode root = TreeNode.arrToTree(new Integer[] { 1, 2, 3, 4, 5, 6, 7 }); System.out.println("\n初始化二叉树\n"); PrintUtil.printTree(root); diff --git a/codes/java/chapter_tree/binary_tree_dfs.java b/codes/java/chapter_tree/binary_tree_dfs.java index 1d3026dfb..d1ef063b0 100644 --- a/codes/java/chapter_tree/binary_tree_dfs.java +++ b/codes/java/chapter_tree/binary_tree_dfs.java @@ -43,8 +43,7 @@ public class binary_tree_dfs { public static void main(String[] args) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode root = TreeNode.arrToTree(new Integer[] { - 1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null}); + TreeNode root = TreeNode.arrToTree(new Integer[] { 1, null, 3, 4, 5 }); System.out.println("\n初始化二叉树\n"); PrintUtil.printTree(root); diff --git a/codes/java/include/PrintUtil.java b/codes/java/include/PrintUtil.java index f21bc17c9..b2593ac1f 100755 --- a/codes/java/include/PrintUtil.java +++ b/codes/java/include/PrintUtil.java @@ -8,6 +8,7 @@ package include; import java.util.*; + class Trunk { Trunk prev; String str; @@ -103,4 +104,11 @@ public class PrintUtil { System.out.println(kv.getKey() + " -> " + kv.getValue()); } } + + public static void printHeap(PriorityQueue queue) { + Integer[] nums = (Integer[])queue.toArray(); + TreeNode root = TreeNode.arrToTree(nums); + + printTree(root); + } } diff --git a/codes/java/include/TreeNode.java b/codes/java/include/TreeNode.java index b98252665..11d457a10 100644 --- a/codes/java/include/TreeNode.java +++ b/codes/java/include/TreeNode.java @@ -22,7 +22,7 @@ public class TreeNode { } /** - * Generate a binary tree with an array + * Generate a binary tree given an array * @param arr * @return */ @@ -32,19 +32,19 @@ public class TreeNode { TreeNode root = new TreeNode(arr[0]); Queue queue = new LinkedList<>() {{ add(root); }}; - int i = 1; + int i = 0; while(!queue.isEmpty()) { TreeNode node = queue.poll(); + if (++i >= arr.length) break; if(arr[i] != null) { node.left = new TreeNode(arr[i]); queue.add(node.left); } - i++; + if (++i >= arr.length) break; if(arr[i] != null) { node.right = new TreeNode(arr[i]); queue.add(node.right); } - i++; } return root; } @@ -71,20 +71,4 @@ public class TreeNode { } return list; } - - /** - * Get a tree node with specific value in a binary tree - * @param root - * @param val - * @return - */ - public static TreeNode getTreeNode(TreeNode root, int val) { - if (root == null) - return null; - if (root.val == val) - return root; - TreeNode left = getTreeNode(root.left, val); - TreeNode right = getTreeNode(root.right, val); - return left != null ? left : right; - } } diff --git a/codes/javascript/chapter_tree/binary_tree_bfs.js b/codes/javascript/chapter_tree/binary_tree_bfs.js index b52f52820..21daaa8dc 100644 --- a/codes/javascript/chapter_tree/binary_tree_bfs.js +++ b/codes/javascript/chapter_tree/binary_tree_bfs.js @@ -28,10 +28,10 @@ function hierOrder(root) { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +var root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log("\n初始化二叉树\n"); printTree(root); /* 层序遍历 */ let list = hierOrder(root); -console.log("\n层序遍历的结点打印序列 = " + list); \ No newline at end of file +console.log("\n层序遍历的结点打印序列 = " + list); diff --git a/codes/javascript/chapter_tree/binary_tree_dfs.js b/codes/javascript/chapter_tree/binary_tree_dfs.js index 51e557336..d23b69b6d 100644 --- a/codes/javascript/chapter_tree/binary_tree_dfs.js +++ b/codes/javascript/chapter_tree/binary_tree_dfs.js @@ -40,7 +40,7 @@ function postOrder(root) { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +var root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log("\n初始化二叉树\n"); printTree(root); @@ -58,4 +58,3 @@ console.log("\n中序遍历的结点打印序列 = " + list); list.length = 0; postOrder(root); console.log("\n后序遍历的结点打印序列 = " + list); - diff --git a/codes/javascript/include/TreeNode.js b/codes/javascript/include/TreeNode.js index 6add12f0c..ce6d667bf 100644 --- a/codes/javascript/include/TreeNode.js +++ b/codes/javascript/include/TreeNode.js @@ -14,7 +14,7 @@ function TreeNode(val, left, right) { } /** -* Generate a binary tree with an array +* Generate a binary tree given an array * @param arr * @return */ @@ -24,20 +24,21 @@ function arrToTree(arr) { let root = new TreeNode(arr[0]); let queue = [root] - let i = 1; - while(queue.length) { + let i = 0; + while (queue.length) { let node = queue.shift(); - if(arr[i] !== null) { + if (++i >= arr.length) break; + if (arr[i] !== null) { node.left = new TreeNode(arr[i]); queue.push(node.left); } - i++; - if(arr[i] !== null) { + if (++i >= arr.length) break; + if (arr[i] !== null) { node.right = new TreeNode(arr[i]); queue.push(node.right); } - i++; } + return root; } diff --git a/codes/python/chapter_tree/binary_search_tree.py b/codes/python/chapter_tree/binary_search_tree.py index db52d57bd..7633b6288 100644 --- a/codes/python/chapter_tree/binary_search_tree.py +++ b/codes/python/chapter_tree/binary_search_tree.py @@ -138,7 +138,7 @@ class BinarySearchTree: """ Driver Code """ if __name__ == "__main__": # 初始化二叉搜索树 - nums = list(range(1, 16)) + nums = list(range(1, 16)) # [1, 2, ..., 15] bst = BinarySearchTree(nums=nums) print("\n初始化的二叉树为\n") print_tree(bst.root) diff --git a/codes/python/chapter_tree/binary_tree.py b/codes/python/chapter_tree/binary_tree.py index d99026352..e00eeb4e5 100644 --- a/codes/python/chapter_tree/binary_tree.py +++ b/codes/python/chapter_tree/binary_tree.py @@ -36,5 +36,5 @@ if __name__ == "__main__": print_tree(n1) # 删除结点 n1.left = n2 - print("\n删除结点 P 后\n"); + print("\n删除结点 P 后\n") print_tree(n1) diff --git a/codes/python/chapter_tree/binary_tree_bfs.py b/codes/python/chapter_tree/binary_tree_bfs.py index 0320a08dd..225a8bd6e 100644 --- a/codes/python/chapter_tree/binary_tree_bfs.py +++ b/codes/python/chapter_tree/binary_tree_bfs.py @@ -32,7 +32,7 @@ def hier_order(root: TreeNode): if __name__ == "__main__": # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 - root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7, None, None, None, None, None, None, None, None]) + root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n初始化二叉树\n") print_tree(root) diff --git a/codes/python/chapter_tree/binary_tree_dfs.py b/codes/python/chapter_tree/binary_tree_dfs.py index 11ee8339c..a1d46e697 100644 --- a/codes/python/chapter_tree/binary_tree_dfs.py +++ b/codes/python/chapter_tree/binary_tree_dfs.py @@ -45,7 +45,7 @@ def post_order(root: typing.Optional[TreeNode]): if __name__ == "__main__": # 初始化二叉树 # 这里借助了一个从数组直接生成二叉树的函数 - root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7, None, None, None, None, None, None, None, None]) + root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n初始化二叉树\n") print_tree(root) diff --git a/codes/python/include/binary_tree.py b/codes/python/include/binary_tree.py index 670fd18ce..9d612a7e2 100644 --- a/codes/python/include/binary_tree.py +++ b/codes/python/include/binary_tree.py @@ -26,39 +26,30 @@ class TreeNode: def list_to_tree(arr): """Generate a binary tree with a list - - Args: - arr ([type]): [description] - - Returns: - [type]: [description] """ if not arr: return None - i = 1 - root = TreeNode(int(arr[0])) - queue = collections.deque() - queue.append(root) + + i = 0 + root = TreeNode(arr[0]) + queue = collections.deque([root]) while queue: node = queue.popleft() + i += 1 + if i >= len(arr): break if arr[i] != None: - node.left = TreeNode(int(arr[i])) + node.left = TreeNode(arr[i]) queue.append(node.left) i += 1 + if i >= len(arr): break if arr[i] != None: - node.right = TreeNode(int(arr[i])) + node.right = TreeNode(arr[i]) queue.append(node.right) - i += 1 + return root def tree_to_list(root): """Serialize a tree into an array - - Args: - root ([type]): [description] - - Returns: - [type]: [description] """ if not root: return [] queue = collections.deque() @@ -75,13 +66,6 @@ def tree_to_list(root): def get_tree_node(root, val): """Get a tree node with specific value in a binary tree - - Args: - root ([type]): [description] - val ([type]): [description] - - Returns: - [type]: [description] """ if not root: return diff --git a/codes/typescript/chapter_tree/binary_tree_bfs.ts b/codes/typescript/chapter_tree/binary_tree_bfs.ts index 1e57ef62d..7ec4a2a5c 100644 --- a/codes/typescript/chapter_tree/binary_tree_bfs.ts +++ b/codes/typescript/chapter_tree/binary_tree_bfs.ts @@ -30,7 +30,7 @@ function hierOrder(root: TreeNode | null): number[] { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +var root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树\n'); printTree(root); diff --git a/codes/typescript/chapter_tree/binary_tree_dfs.ts b/codes/typescript/chapter_tree/binary_tree_dfs.ts index d9e982df1..a61855af8 100644 --- a/codes/typescript/chapter_tree/binary_tree_dfs.ts +++ b/codes/typescript/chapter_tree/binary_tree_dfs.ts @@ -47,7 +47,7 @@ function postOrder(root: TreeNode | null): void { /* Driver Code */ /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 -const root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); console.log('\n初始化二叉树\n'); printTree(root); diff --git a/codes/typescript/module/TreeNode.ts b/codes/typescript/module/TreeNode.ts index 5a886763e..0e3425006 100644 --- a/codes/typescript/module/TreeNode.ts +++ b/codes/typescript/module/TreeNode.ts @@ -20,7 +20,7 @@ class TreeNode { } /** - * Generate a binary tree with an array + * Generate a binary tree given an array * @param arr * @return */ @@ -31,19 +31,19 @@ function arrToTree(arr: (number | null)[]): TreeNode | null { const root = new TreeNode(arr[0] as number); const queue = [root]; - let i = 1; + let i = 0; while (queue.length) { let node = queue.shift() as TreeNode; + if (++i >= arr.length) break; if (arr[i] !== null) { node.left = new TreeNode(arr[i] as number); queue.push(node.left); } - i++; + if (++i >= arr.length) break; if (arr[i] !== null) { node.right = new TreeNode(arr[i] as number); queue.push(node.right); } - i++; } return root; } diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md index def4cde5b..b0151622f 100644 --- a/docs/chapter_heap/heap.md +++ b/docs/chapter_heap/heap.md @@ -2,26 +2,48 @@ 「堆 Heap」是一种特殊的树状数据结构,并且是一颗「完全二叉树」。堆主要分为两种: -- 「大顶堆 Max Heap」,任意父结点的值 > 其子结点的值,因此根结点的值最大; -- 「小顶堆 Min Heap」,任意父结点的值 < 其子结点的值,因此根结点的值最小; +- 「大顶堆 Max Heap」,任意结点的值 $\geq$ 其子结点的值,因此根结点的值最大; +- 「小顶堆 Min Heap」,任意结点的值 $\leq$ 其子结点的值,因此根结点的值最小; (图) !!! tip "" - 大顶堆和小顶堆的定义、性质、操作本质上是一样的。区别只是大顶堆在求最大值,小顶堆在求最小值。在下文中,我们将统一用「大顶堆」来举例,「小顶堆」的用法与实现可以简单地将所有 $>$ ($<$) 替换为 $<$ ($>$) 即可。 + 大顶堆和小顶堆的定义、性质、操作本质上是相同的,区别只是大顶堆在求最大值,小顶堆在求最小值。 ## 堆常用操作 -堆的初始化。 +值得说明的是,多数编程语言提供的是「优先队列 Priority Queue」,其是一种抽象数据结构,**定义为具有出队优先级的队列**。 -获取堆顶元素。 +而恰好,堆的定义与优先队列的操作逻辑完全吻合,大顶堆就是一个元素从大到小出队的优先队列。从使用角度看,我们可以将「优先队列」和「堆」理解为等价的数据结构,下文将统一使用 “堆” 这个名称。 -添加与删除元素。 +堆的常用操作见下表(方法命名以 Java 为例)。 + +

Table. 堆的常用操作

+ +
+ +| 方法 | 描述 | +| --------- | -------------------------------------------- | +| add() | 元素入堆 | +| poll() | 堆顶元素出堆 | +| peek() | 访问堆顶元素(大 / 小顶堆分别为最大 / 小值) | +| size() | 获取堆的元素数量 | +| isEmpty() | 判断堆是否为空 | + +
+ +```java + +``` ## 堆的实现 -在二叉树章节中,我们讲过二叉树的数组表示方法,并且提到完全二叉树非常适合用数组来表示,因此我们一般使用「数组」来存储「堆」。 +!!! tip + + 下文使用「大顶堆」来举例,「小顶堆」的用法与实现可以简单地将所有 $>$ ($<$) 替换为 $<$ ($>$) 即可。 + +我们一般使用「数组」来存储「堆」,这是因为完全二叉树非常适合用数组来表示(在二叉树章节有详细解释)。 diff --git a/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png b/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png index b7922fcb3e7a364f2ef795cdcc8108852c2abdce..d95c48f078fb4379531212f15945476bdd1aafd9 100644 GIT binary patch literal 79400 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 E1C(Ef#sB~S literal 77839 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 O_L$~y1wIpv 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%0Du>L0dEV7pFYgfQpxr;h<=Oz~lZJVKz8HnpS4bcm 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�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 bvv~h|)ZV2eITgJM1}-m;L;E;;LbLw|6_qN3 From 4839c234329c9365bd73668cb17b198e6345498d Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sun, 8 Jan 2023 19:04:24 +0800 Subject: [PATCH 06/21] Remove heap.md for temporary. --- docs/chapter_heap/heap.md | 54 --------------------------------------- 1 file changed, 54 deletions(-) delete mode 100644 docs/chapter_heap/heap.md diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md deleted file mode 100644 index b0151622f..000000000 --- a/docs/chapter_heap/heap.md +++ /dev/null @@ -1,54 +0,0 @@ -# 堆 - -「堆 Heap」是一种特殊的树状数据结构,并且是一颗「完全二叉树」。堆主要分为两种: - -- 「大顶堆 Max Heap」,任意结点的值 $\geq$ 其子结点的值,因此根结点的值最大; -- 「小顶堆 Min Heap」,任意结点的值 $\leq$ 其子结点的值,因此根结点的值最小; - -(图) - -!!! tip "" - - 大顶堆和小顶堆的定义、性质、操作本质上是相同的,区别只是大顶堆在求最大值,小顶堆在求最小值。 - -## 堆常用操作 - -值得说明的是,多数编程语言提供的是「优先队列 Priority Queue」,其是一种抽象数据结构,**定义为具有出队优先级的队列**。 - -而恰好,堆的定义与优先队列的操作逻辑完全吻合,大顶堆就是一个元素从大到小出队的优先队列。从使用角度看,我们可以将「优先队列」和「堆」理解为等价的数据结构,下文将统一使用 “堆” 这个名称。 - -堆的常用操作见下表(方法命名以 Java 为例)。 - -

Table. 堆的常用操作

- -
- -| 方法 | 描述 | -| --------- | -------------------------------------------- | -| add() | 元素入堆 | -| poll() | 堆顶元素出堆 | -| peek() | 访问堆顶元素(大 / 小顶堆分别为最大 / 小值) | -| size() | 获取堆的元素数量 | -| isEmpty() | 判断堆是否为空 | - -
- -```java - -``` - -## 堆的实现 - -!!! tip - - 下文使用「大顶堆」来举例,「小顶堆」的用法与实现可以简单地将所有 $>$ ($<$) 替换为 $<$ ($>$) 即可。 - -我们一般使用「数组」来存储「堆」,这是因为完全二叉树非常适合用数组来表示(在二叉树章节有详细解释)。 - - - -## 堆常见应用 - -- 优先队列。 -- 堆排序。 -- 获取数据 Top K 大(小)元素。 From 3ba37dba3a55f02b46484a41fcfeacc2881c6a47 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sun, 8 Jan 2023 19:08:07 +0800 Subject: [PATCH 07/21] Fix the test case of the binary tree dfs in Java. --- codes/java/chapter_tree/binary_tree_dfs.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codes/java/chapter_tree/binary_tree_dfs.java b/codes/java/chapter_tree/binary_tree_dfs.java index d1ef063b0..ad5337f22 100644 --- a/codes/java/chapter_tree/binary_tree_dfs.java +++ b/codes/java/chapter_tree/binary_tree_dfs.java @@ -43,7 +43,7 @@ public class binary_tree_dfs { public static void main(String[] args) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - TreeNode root = TreeNode.arrToTree(new Integer[] { 1, null, 3, 4, 5 }); + TreeNode root = TreeNode.arrToTree(new Integer[] { 1, 2, 3, 4, 5, 6, 7 }); System.out.println("\n初始化二叉树\n"); PrintUtil.printTree(root); From 73e345283841496b6ed37df8cf838434c9530ee8 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sun, 8 Jan 2023 19:41:05 +0800 Subject: [PATCH 08/21] Add Swift language blocks to the docs. --- .../worst_best_time_complexity.c | 12 +-- codes/csharp/hello-algo.csproj | 4 +- .../chapter_array_and_linkedlist/array.swift | 12 +-- .../linked_list.md | 100 ++++++++++++------ docs/chapter_array_and_linkedlist/list.md | 43 ++++++++ .../time_complexity.md | 12 +-- docs/chapter_hashing/hash_map.md | 50 ++++++--- docs/chapter_preface/about_the_book.md | 13 +++ docs/chapter_searching/binary_search.md | 18 ++++ docs/chapter_searching/hashing_search.md | 12 +++ docs/chapter_searching/linear_search.md | 12 +++ docs/chapter_sorting/bubble_sort.md | 12 +++ docs/chapter_sorting/insertion_sort.md | 6 ++ docs/chapter_sorting/merge_sort.md | 6 ++ docs/chapter_sorting/quick_sort.md | 28 ++++- docs/chapter_stack_and_queue/deque.md | 6 ++ docs/chapter_stack_and_queue/queue.md | 18 ++++ docs/chapter_stack_and_queue/stack.md | 18 ++++ docs/chapter_tree/avl_tree.md | 48 +++++++++ docs/chapter_tree/binary_search_tree.md | 18 ++++ docs/chapter_tree/binary_tree.md | 24 +++++ docs/chapter_tree/binary_tree_traversal.md | 12 +++ 22 files changed, 414 insertions(+), 70 deletions(-) diff --git a/codes/c/chapter_computational_complexity/worst_best_time_complexity.c b/codes/c/chapter_computational_complexity/worst_best_time_complexity.c index 9173f32d4..2570ff3c3 100644 --- a/codes/c/chapter_computational_complexity/worst_best_time_complexity.c +++ b/codes/c/chapter_computational_complexity/worst_best_time_complexity.c @@ -15,12 +15,12 @@ int *randomNumbers(int n) { nums[i] = i + 1; } // 随机打乱数组元素 - for (int i = n - 1; i > 0; i--) { - int j = rand() % (i + 1); - int temp = nums[i]; - nums[i] = nums[j]; - nums[j] = temp; - } + for (int i = n - 1; i > 0; i--) { + int j = rand() % (i + 1); + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } return nums; } diff --git a/codes/csharp/hello-algo.csproj b/codes/csharp/hello-algo.csproj index 2ef4da851..d96d728b1 100644 --- a/codes/csharp/hello-algo.csproj +++ b/codes/csharp/hello-algo.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/codes/swift/chapter_array_and_linkedlist/array.swift b/codes/swift/chapter_array_and_linkedlist/array.swift index c0c7ab9b9..08cd0707f 100644 --- a/codes/swift/chapter_array_and_linkedlist/array.swift +++ b/codes/swift/chapter_array_and_linkedlist/array.swift @@ -4,7 +4,7 @@ * Author: nuomi1 (nuomi1@qq.com) */ -// 随机返回一个数组元素 +/* 随机返回一个数组元素 */ func randomAccess(nums: [Int]) -> Int { // 在区间 [0, nums.count) 中随机抽取一个数字 let randomIndex = nums.indices.randomElement()! @@ -13,7 +13,7 @@ func randomAccess(nums: [Int]) -> Int { return randomNum } -// 扩展数组长度 +/* 扩展数组长度 */ func extend(nums: [Int], enlarge: Int) -> [Int] { // 初始化一个扩展长度后的数组 var res = Array(repeating: 0, count: nums.count + enlarge) @@ -25,7 +25,7 @@ func extend(nums: [Int], enlarge: Int) -> [Int] { return res } -// 在数组的索引 index 处插入元素 num +/* 在数组的索引 index 处插入元素 num */ func insert(nums: inout [Int], num: Int, index: Int) { // 把索引 index 以及之后的所有元素向后移动一位 for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { @@ -35,7 +35,7 @@ func insert(nums: inout [Int], num: Int, index: Int) { nums[index] = num } -// 删除索引 index 处元素 +/* 删除索引 index 处元素 */ func remove(nums: inout [Int], index: Int) { let count = nums.count // 把索引 index 之后的所有元素向前移动一位 @@ -44,7 +44,7 @@ func remove(nums: inout [Int], index: Int) { } } -// 遍历数组 +/* 遍历数组 */ func traverse(nums: [Int]) { var count = 0 // 通过索引遍历数组 @@ -57,7 +57,7 @@ func traverse(nums: [Int]) { } } -// 在数组中查找指定元素 +/* 在数组中查找指定元素 */ func find(nums: [Int], target: Int) -> Int { for i in nums.indices { if nums[i] == target { diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index f054802d4..aa9c93ec7 100644 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -112,6 +112,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + + ``` + **尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。 **链表初始化方法**。建立链表分为两步,第一步是初始化各个结点对象,第二步是构建引用指向关系。完成后,即可以从链表的首个结点(即头结点)出发,访问其余所有的结点。 @@ -122,7 +128,7 @@ comments: true === "Java" - ```java title="" + ```java title="linked_list.java" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 ListNode n0 = new ListNode(1); @@ -139,7 +145,7 @@ comments: true === "C++" - ```cpp title="" + ```cpp title="linked_list.cpp" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 ListNode* n0 = new ListNode(1); @@ -156,7 +162,7 @@ comments: true === "Python" - ```python title="" + ```python title="linked_list.py" """ 初始化链表 1 -> 3 -> 2 -> 5 -> 4 """ # 初始化各个结点 n0 = ListNode(1) @@ -173,7 +179,7 @@ comments: true === "Go" - ```go title="" + ```go title="linked_list.go" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 n0 := NewListNode(1) @@ -191,7 +197,7 @@ comments: true === "JavaScript" - ```js title="" + ```js title="linked_list.js" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 const n0 = new ListNode(1); @@ -208,7 +214,7 @@ comments: true === "TypeScript" - ```typescript title="" + ```typescript title="linked_list.ts" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 const n0 = new ListNode(1); @@ -225,13 +231,13 @@ comments: true === "C" - ```c title="" + ```c title="linked_list.c" ``` === "C#" - ```csharp title="" + ```csharp title="linked_list.cs" /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ // 初始化各个结点 ListNode n0 = new ListNode(1); @@ -246,6 +252,12 @@ comments: true n3.next = n4; ``` +=== "Swift" + + ```swift title="linked_list.swift" + + ``` + ## 链表优点 **在链表中,插入与删除结点的操作效率高**。例如,如果想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。在链表中删除某个结点也很方便,只需要改变一个结点指针即可。 @@ -256,7 +268,7 @@ comments: true === "Java" - ```java title="" + ```java title="linked_list.java" /* 在链表的结点 n0 之后插入结点 P */ void insert(ListNode n0, ListNode P) { ListNode n1 = n0.next; @@ -277,7 +289,7 @@ comments: true === "C++" - ```cpp title="" + ```cpp title="linked_list.cpp" /* 在链表的结点 n0 之后插入结点 P */ void insert(ListNode* n0, ListNode* P) { ListNode* n1 = n0->next; @@ -300,7 +312,7 @@ comments: true === "Python" - ```python title="" + ```python title="linked_list.py" """ 在链表的结点 n0 之后插入结点 P """ def insert(n0, P): n1 = n0.next @@ -319,7 +331,7 @@ comments: true === "Go" - ```go title="" + ```go title="linked_list.go" /* 在链表的结点 n0 之后插入结点 P */ func insert(n0 *ListNode, P *ListNode) { n1 := n0.Next @@ -341,7 +353,7 @@ comments: true === "JavaScript" - ```js title="" + ```js title="linked_list.js" /* 在链表的结点 n0 之后插入结点 P */ function insert(n0, P) { let n1 = n0.next; @@ -362,7 +374,7 @@ comments: true === "TypeScript" - ```typescript title="" + ```typescript title="linked_list.ts" /* 在链表的结点 n0 之后插入结点 P */ function insert(n0: ListNode, P: ListNode): void { const n1 = n0.next; @@ -383,13 +395,13 @@ comments: true === "C" - ```c title="" + ```c title="linked_list.c" ``` === "C#" - ```csharp title="" + ```csharp title="linked_list.cs" // 在链表的结点 n0 之后插入结点 P void Insert(ListNode n0, ListNode P) { @@ -410,13 +422,19 @@ comments: true } ``` +=== "Swift" + + ```swift title="linked_list.swift" + + ``` + ## 链表缺点 **链表访问结点效率低**。上节提到,数组可以在 $O(1)$ 时间下访问任意元素,但链表无法直接访问任意结点。这是因为计算机需要从头结点出发,一个一个地向后遍历到目标结点。例如,倘若想要访问链表索引为 `index` (即第 `index + 1` 个)的结点,那么需要 `index` 次访问操作。 === "Java" - ```java title="" + ```java title="linked_list.java" /* 访问链表中索引为 index 的结点 */ ListNode access(ListNode head, int index) { for (int i = 0; i < index; i++) { @@ -430,7 +448,7 @@ comments: true === "C++" - ```cpp title="" + ```cpp title="linked_list.cpp" /* 访问链表中索引为 index 的结点 */ ListNode* access(ListNode* head, int index) { for (int i = 0; i < index; i++) { @@ -444,7 +462,7 @@ comments: true === "Python" - ```python title="" + ```python title="linked_list.py" """ 访问链表中索引为 index 的结点 """ def access(head, index): for _ in range(index): @@ -456,7 +474,7 @@ comments: true === "Go" - ```go title="" + ```go title="linked_list.go" /* 访问链表中索引为 index 的结点 */ func access(head *ListNode, index int) *ListNode { for i := 0; i < index; i++ { @@ -471,7 +489,7 @@ comments: true === "JavaScript" - ```js title="" + ```js title="linked_list.js" /* 访问链表中索引为 index 的结点 */ function access(head, index) { for (let i = 0; i < index; i++) { @@ -485,7 +503,7 @@ comments: true === "TypeScript" - ```typescript title="" + ```typescript title="linked_list.ts" /* 访问链表中索引为 index 的结点 */ function access(head: ListNode | null, index: number): ListNode | null { for (let i = 0; i < index; i++) { @@ -500,13 +518,13 @@ comments: true === "C" - ```c title="" + ```c title="linked_list.c" ``` === "C#" - ```csharp title="" + ```csharp title="linked_list.cs" // 访问链表中索引为 index 的结点 ListNode Access(ListNode head, int index) { @@ -520,6 +538,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="linked_list.swift" + + ``` + **链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 ## 链表常用操作 @@ -528,7 +552,7 @@ comments: true === "Java" - ```java title="" + ```java title="linked_list.java" /* 在链表中查找值为 target 的首个结点 */ int find(ListNode head, int target) { int index = 0; @@ -544,7 +568,7 @@ comments: true === "C++" - ```cpp title="" + ```cpp title="linked_list.cpp" /* 在链表中查找值为 target 的首个结点 */ int find(ListNode* head, int target) { int index = 0; @@ -560,7 +584,7 @@ comments: true === "Python" - ```python title="" + ```python title="linked_list.py" """ 在链表中查找值为 target 的首个结点 """ def find(head, target): index = 0 @@ -574,7 +598,7 @@ comments: true === "Go" - ```go title="" + ```go title="linked_list.go" /* 在链表中查找值为 target 的首个结点 */ func find(head *ListNode, target int) int { index := 0 @@ -591,7 +615,7 @@ comments: true === "JavaScript" - ```js title="" + ```js title="linked_list.js" /* 在链表中查找值为 target 的首个结点 */ function find(head, target) { let index = 0; @@ -608,7 +632,7 @@ comments: true === "TypeScript" - ```typescript title="" + ```typescript title="linked_list.ts" /* 在链表中查找值为 target 的首个结点 */ function find(head: ListNode | null, target: number): number { let index = 0; @@ -625,13 +649,13 @@ comments: true === "C" - ```c title="" + ```c title="linked_list.c" ``` === "C#" - ```csharp title="" + ```csharp title="linked_list.cs" // 在链表中查找值为 target 的首个结点 int Find(ListNode head, int target) { @@ -647,6 +671,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="linked_list.swift" + + ``` + ## 常见链表类型 **单向链表**。即上述介绍的普通链表。单向链表的结点有「值」和指向下一结点的「指针(引用)」两项数据。我们将首个结点称为头结点,尾结点指向 `null` 。 @@ -760,6 +790,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + + ``` + ![linkedlist_common_types](linked_list.assets/linkedlist_common_types.png)

Fig. 常见链表类型

diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index 19de6d778..d1109d65b 100644 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -91,6 +91,12 @@ comments: true List list = numbers.ToList(); ``` +=== "Swift" + + ```swift title="list.swift" + + ``` + **访问与更新元素**。列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 === "Java" @@ -169,6 +175,12 @@ comments: true list[1] = 0; // 将索引 1 处的元素更新为 0 ``` +=== "Swift" + + ```swift title="list.swift" + + ``` + **在列表中添加、插入、删除元素**。相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 === "Java" @@ -317,6 +329,12 @@ comments: true list.RemoveAt(3); ``` +=== "Swift" + + ```swift title="list.swift" + + ``` + **遍历列表**。与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 === "Java" @@ -437,6 +455,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="list.swift" + + ``` + **拼接两个列表**。再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 === "Java" @@ -502,6 +526,12 @@ comments: true list.AddRange(list1); // 将列表 list1 拼接到 list 之后 ``` +=== "Swift" + + ```swift title="list.swift" + + ``` + **排序列表**。排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 === "Java" @@ -559,6 +589,12 @@ comments: true list.Sort(); // 排序后,列表元素从小到大排列 ``` +=== "Swift" + + ```swift title="list.swift" + + ``` + ## 列表简易实现 * 为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点: @@ -1220,3 +1256,10 @@ comments: true } } ``` + +=== "Swift" + + ```swift title="my_list.swift" + + ``` + diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 217981ae8..7b0e66fbd 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -421,12 +421,12 @@ $$ ```go title="" func algorithm(n int) { - a := 1 // +1 - a = a + 1 // +1 - a = a * 2 // +1 + a := 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 // 循环 n 次 - for i := 0; i < n; i++ { // +1 - fmt.Println(a) // +1 + for i := 0; i < n; i++ { // +1 + fmt.Println(a) // +1 } } ``` @@ -2657,7 +2657,7 @@ $$ === "Swift" - ```swift title="" + ```swift title="worst_best_time_complexity.swift" // 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 func randomNumbers(n: Int) -> [Int] { // 生成数组 nums = { 1, 2, 3, ..., n } diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index d8244d80a..3a69d88fc 100644 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -108,25 +108,25 @@ comments: true === "Go" - ```go title="hash_map_test.go" - /* 初始化哈希表 */ - mapp := make(map[int]string) + ```go title="hash_map.go" + /* 初始化哈希表 */ + mapp := make(map[int]string) - /* 添加操作 */ - // 在哈希表中添加键值对 (key, value) - mapp[12836] = "小哈" - mapp[15937] = "小啰" - mapp[16750] = "小算" - mapp[13276] = "小法" - mapp[10583] = "小鸭" + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + mapp[12836] = "小哈" + mapp[15937] = "小啰" + mapp[16750] = "小算" + mapp[13276] = "小法" + mapp[10583] = "小鸭" - /* 查询操作 */ - // 向哈希表输入键 key ,得到值 value - name := mapp[15937] + /* 查询操作 */ + // 向哈希表输入键 key ,得到值 value + name := mapp[15937] - /* 删除操作 */ - // 在哈希表中删除键值对 (key, value) - delete(mapp, 10583) + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + delete(mapp, 10583) ``` === "JavaScript" @@ -207,6 +207,12 @@ comments: true map.Remove(10583); ``` +=== "Swift" + + ```swift title="hash_map.swift" + + ``` + 遍历哈希表有三种方式,即 **遍历键值对、遍历键、遍历值**。 === "Java" @@ -339,6 +345,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="hash_map.swift" + + ``` + ## 哈希函数 哈希表中存储元素的数据结构被称为「桶 Bucket」,底层实现可能是数组、链表、二叉树(红黑树),或是它们的组合。 @@ -756,6 +768,12 @@ $$ } ``` +=== "Swift" + + ```swift title="array_hash_map.swift" + + ``` + ## 哈希冲突 细心的同学可能会发现,**哈希函数 $f(x) = x \% 100$ 会在某些情况下失效**。具体地,当输入的 key 后两位相同时,哈希函数的计算结果也相同,指向同一个 value 。例如,分别查询两个学号 12836 和 20336 ,则有 diff --git a/docs/chapter_preface/about_the_book.md b/docs/chapter_preface/about_the_book.md index cb926b941..2fba23502 100644 --- a/docs/chapter_preface/about_the_book.md +++ b/docs/chapter_preface/about_the_book.md @@ -192,6 +192,19 @@ comments: true */ ``` +=== "Swift" + + ```swift title="" + /* 标题注释,用于标注函数、类、测试样例等 */ + + // 内容注释,用于详解代码 + + /** + * 多行 + * 注释 + */ + ``` + """ 在 Java, C, C++, C#, Go, JS, TS 的代码注释中,`/* ... */` 用于注释函数、类、测试样例等标题, `// ...` 用于解释代码内容;类似地,在 Python 中,`""" ... """` 用于注释标题, `# ...` 用于解释代码。 diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md index 8827588ef..466e76658 100644 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -210,6 +210,12 @@ $$ } ``` +=== "Swift" + + ```swift title="binary_search.swift" + + ``` + ### “左闭右开”实现 当然,我们也可以使用“左闭右开”的表示方法,写出相同功能的二分查找代码。 @@ -374,6 +380,12 @@ $$ } ``` +=== "Swift" + + ```swift title="binary_search.swift" + + ``` + ### 两种表示对比 对比下来,两种表示的代码写法有以下不同点: @@ -460,6 +472,12 @@ $$ int m = i + (j - i) / 2; ``` +=== "Swift" + + ```swift title="" + + ``` + ## 复杂度分析 **时间复杂度 $O(\log n)$ :** 其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。 diff --git a/docs/chapter_searching/hashing_search.md b/docs/chapter_searching/hashing_search.md index a70a55483..12746d2ef 100644 --- a/docs/chapter_searching/hashing_search.md +++ b/docs/chapter_searching/hashing_search.md @@ -95,6 +95,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="hashing_search.swift" + + ``` + 再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。 ![hash_search_listnode](hashing_search.assets/hash_search_listnode.png) @@ -179,6 +185,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="hashing_search.swift" + + ``` + ## 复杂度分析 **时间复杂度:** $O(1)$ ,哈希表的查找操作使用 $O(1)$ 时间。 diff --git a/docs/chapter_searching/linear_search.md b/docs/chapter_searching/linear_search.md index 1c07027e6..be98c013d 100644 --- a/docs/chapter_searching/linear_search.md +++ b/docs/chapter_searching/linear_search.md @@ -122,6 +122,12 @@ comments: true ``` +=== "Swift" + + ```swift title="linear_search.swift" + + ``` + 再比如,我们想要在给定一个目标结点值 `target` ,返回此结点对象,也可以在链表中进行线性查找。 === "Java" @@ -238,6 +244,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="linear_search.swift" + + ``` + ## 复杂度分析 **时间复杂度 $O(n)$ :** 其中 $n$ 为数组或链表长度。 diff --git a/docs/chapter_sorting/bubble_sort.md b/docs/chapter_sorting/bubble_sort.md index bb35fabe4..1dbc56475 100644 --- a/docs/chapter_sorting/bubble_sort.md +++ b/docs/chapter_sorting/bubble_sort.md @@ -212,6 +212,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="bubble_sort.swift" + + ``` + ## 算法特性 **时间复杂度 $O(n^2)$ :** 各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。 @@ -414,3 +420,9 @@ comments: true } } ``` + +=== "Swift" + + ```swift title="bubble_sort.swift" + + ``` diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md index 85016f718..679182f42 100644 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -175,6 +175,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="insertion_sort.swift" + + ``` + ## 算法特性 **时间复杂度 $O(n^2)$ :** 最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。 diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index 48a5046c7..396baa089 100644 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -388,6 +388,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="merge_sort.swift" + + ``` + 下面重点解释一下合并方法 `merge()` 的流程: 1. 初始化一个辅助数组 `tmp` 暂存待合并区间 `[left, right]` 内的元素,后续通过覆盖原数组 `nums` 的元素来实现合并; diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index ebc161caa..f8dcc3238 100644 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -223,6 +223,12 @@ comments: true ``` +=== "Swift" + + ```swift title="quick_sort.swift" + + ``` + !!! note "快速排序的分治思想" 哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题**。 @@ -359,6 +365,12 @@ comments: true ``` +=== "Swift" + + ```swift title="quick_sort.swift" + + ``` + ## 算法特性 **平均时间复杂度 $O(n \log n)$ :** 平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。 @@ -574,6 +586,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="quick_sort.swift" + + ``` + ## 尾递归优化 **普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。 @@ -652,10 +670,10 @@ comments: true // 对两个子数组中较短的那个执行快排 if pivot-left < right-pivot { quickSort(nums, left, pivot-1) // 递归排序左子数组 - left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right] + left = pivot + 1 // 剩余待排序区间为 [pivot + 1, right] } else { quickSort(nums, pivot+1, right) // 递归排序右子数组 - right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1] + right = pivot - 1 // 剩余待排序区间为 [left, pivot - 1] } } } @@ -734,3 +752,9 @@ comments: true } } ``` + +=== "Swift" + + ```swift title="quick_sort.swift" + + ``` diff --git a/docs/chapter_stack_and_queue/deque.md b/docs/chapter_stack_and_queue/deque.md index c178c0e0a..52b0540d5 100644 --- a/docs/chapter_stack_and_queue/deque.md +++ b/docs/chapter_stack_and_queue/deque.md @@ -192,3 +192,9 @@ comments: true /* 判断双向队列是否为空 */ bool isEmpty = deque.Count == 0; ``` + +=== "Swift" + + ```swift title="deque.swift" + + ``` diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index 8a964d3b7..ce47edb38 100644 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -228,6 +228,12 @@ comments: true bool isEmpty = queue.Count() == 0; ``` +=== "Swift" + + ```swift title="queue.swift" + + ``` + ## 队列实现 队列需要一种可以在一端添加,并在另一端删除的数据结构,也可以使用链表或数组来实现。 @@ -612,6 +618,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="linkedlist_queue.swift" + + ``` + ### 基于数组的实现 数组的删除首元素的时间复杂度为 $O(n)$ ,因此不适合直接用来实现队列。然而,我们可以借助两个指针 `front` , `rear` 来分别记录队首和队尾的索引位置,在入队 / 出队时分别将 `front` / `rear` 向后移动一位即可,这样每次仅需操作一个元素,时间复杂度降至 $O(1)$ 。 @@ -1015,6 +1027,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="array_queue.swift" + + ``` + ## 队列典型应用 - **淘宝订单**。购物者下单后,订单就被加入到队列之中,随后系统再根据顺序依次处理队列中的订单。在双十一时,在短时间内会产生海量的订单,如何处理「高并发」则是工程师们需要重点思考的问题。 diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index 985a4cf66..374de7424 100644 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -228,6 +228,12 @@ comments: true bool isEmpty = stack.Count()==0; ``` +=== "Swift" + + ```swift title="stack.swift" + + ``` + ## 栈的实现 为了更加清晰地了解栈的运行机制,接下来我们来自己动手实现一个栈类。 @@ -591,6 +597,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="linkedlist_stack.swift" + + ``` + ### 基于数组的实现 使用「数组」实现栈时,将数组的尾部当作栈顶,这样可以保证入栈与出栈操作的时间复杂度都为 $O(1)$ 。准确地说,由于入栈的元素可能是源源不断的,我们需要使用可以动态扩容的「列表」。 @@ -870,6 +882,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="array_stack.swift" + + ``` + !!! tip 某些语言并未专门提供栈类,但我们可以直接把该语言的「数组」或「链表」看作栈来使用,并通过“脑补”来屏蔽无关操作,而无需像上述代码去特意包装一层。 diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index fe08828c1..43e0d9f45 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -94,6 +94,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + 「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1** 。我们封装两个工具函数,分别用于获取与更新结点的高度。 === "Java" @@ -176,6 +182,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ### 结点平衡因子 结点的「平衡因子 Balance Factor」是 **结点的左子树高度减去右子树高度**,并定义空结点的平衡因子为 0 。同样地,我们将获取结点平衡因子封装成函数,以便后续使用。 @@ -247,6 +259,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + !!! note 设平衡因子为 $f$ ,则一棵 AVL 树的任意结点的平衡因子皆满足 $-1 \le f \le 1$ 。 @@ -361,6 +379,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ### Case 2 - 左旋 类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。 @@ -457,6 +481,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ### Case 3 - 先左后右 对于下图的失衡结点 3 ,**单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。 @@ -626,6 +656,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ## AVL 树常用操作 ### 插入结点 @@ -744,6 +780,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ### 删除结点 「AVL 树」删除结点操作与「二叉搜索树」删除结点操作总体相同。类似地,**在删除结点后,也需要从底至顶地执行旋转操作,使所有失衡结点恢复平衡**。 @@ -902,6 +944,12 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 } ``` +=== "Swift" + + ```swift title="avl_tree.swift" + + ``` + ### 查找结点 「AVL 树」的结点查找操作与「二叉搜索树」一致,在此不再赘述。 diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index 60a5598bd..979612852 100644 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -192,6 +192,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_search_tree.swift" + + ``` + ### 插入结点 给定一个待插入元素 `num` ,为了保持二叉搜索树“左子树 < 根结点 < 右子树”的性质,插入操作分为两步: @@ -422,6 +428,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_search_tree.swift" + + ``` + 为了插入结点,需要借助 **辅助结点 `prev`** 保存上一轮循环的结点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父结点,从而完成结点插入操作。 与查找结点相同,插入结点使用 $O(\log n)$ 时间。 @@ -808,6 +820,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_search_tree.swift" + + ``` + ## 二叉搜索树的优势 假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为: diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 7977ab6a2..077c03e0d 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -106,6 +106,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="" + + ``` + 结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。 除了叶结点外,每个结点都有子结点和子树。例如,若将上图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 以下的树」和「结点 5 以下的树」。 @@ -263,6 +269,12 @@ comments: true n2.right = n5; ``` +=== "Swift" + + ```swift title="binary_tree.swift" + + ``` + **插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。 ![binary_tree_add_remove](binary_tree.assets/binary_tree_add_remove.png) @@ -358,6 +370,12 @@ comments: true n1.left = n2; ``` +=== "Swift" + + ```swift title="binary_tree.swift" + + ``` + !!! note 插入结点会改变二叉树的原有逻辑结构,删除结点往往意味着删除了该结点的所有子树。因此,二叉树中的插入与删除一般都是由一套操作配合完成的,这样才能实现有意义的操作。 @@ -495,6 +513,12 @@ comments: true int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` +=== "Swift" + + ```swift title="" + + ``` + ![array_representation_with_empty](binary_tree.assets/array_representation_with_empty.png) 回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。 diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index f46348412..c64077072 100644 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -185,6 +185,12 @@ comments: true ``` +=== "Swift" + + ```swift title="binary_tree_bfs.swift" + + ``` + ## 前序、中序、后序遍历 相对地,前、中、后序遍历皆属于「深度优先遍历 Depth-First Traversal」,其体现着一种“先走到尽头,再回头继续”的回溯遍历方式。 @@ -443,6 +449,12 @@ comments: true } ``` +=== "Swift" + + ```swift title="binary_tree_dfs.swift" + + ``` + !!! note 使用循环一样可以实现前、中、后序遍历,但代码相对繁琐,有兴趣的同学可以自行实现。 From 230c7723d53746c1fcf8c3d08849d5d67730336d Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sun, 8 Jan 2023 19:55:08 +0800 Subject: [PATCH 09/21] Update comment format of array.swift --- .../chapter_array_and_linkedlist/array.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/codes/swift/chapter_array_and_linkedlist/array.swift b/codes/swift/chapter_array_and_linkedlist/array.swift index 08cd0707f..344dc5abc 100644 --- a/codes/swift/chapter_array_and_linkedlist/array.swift +++ b/codes/swift/chapter_array_and_linkedlist/array.swift @@ -69,34 +69,34 @@ func find(nums: [Int], target: Int) -> Int { @main enum _Array { - // Driver Code + /* Driver Code */ static func main() { - // 初始化数组 + /* 初始化数组 */ let arr = Array(repeating: 0, count: 5) print("数组 arr = \(arr)") var nums = [1, 3, 2, 5, 4] print("数组 nums = \(nums)") - // 随机访问 + /* 随机访问 */ let randomNum = randomAccess(nums: nums) print("在 nums 中获取随机元素 \(randomNum)") - // 长度扩展 + /* 长度扩展 */ nums = extend(nums: nums, enlarge: 3) print("将数组长度扩展至 8 ,得到 nums = \(nums)") - // 插入元素 + /* 插入元素 */ insert(nums: &nums, num: 6, index: 3) print("在索引 3 处插入数字 6 ,得到 nums = \(nums)") - // 删除元素 + /* 删除元素 */ remove(nums: &nums, index: 2) print("删除索引 2 处的元素,得到 nums = \(nums)") - // 遍历数组 + /* 遍历数组 */ traverse(nums: nums) - // 查找元素 + /* 查找元素 */ let index = find(nums: nums, target: 3) print("在 nums 中查找元素 3 ,得到索引 = \(index)") } From 7556558704ec593d0509869c8b5a0d5a297baa7e Mon Sep 17 00:00:00 2001 From: nuomi1 Date: Sun, 8 Jan 2023 20:22:59 +0800 Subject: [PATCH 10/21] feat: add Swift codes for linked_list article --- codes/swift/Package.swift | 2 + .../linked_list.swift | 91 +++++++++++++++++++ codes/swift/utils/PrintUtil.swift | 10 ++ .../linked_list.md | 73 ++++++++++++++- 4 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 codes/swift/chapter_array_and_linkedlist/linked_list.swift diff --git a/codes/swift/Package.swift b/codes/swift/Package.swift index d9368354b..4cf3601c7 100644 --- a/codes/swift/Package.swift +++ b/codes/swift/Package.swift @@ -10,6 +10,7 @@ let package = Package( .executable(name: "space_complexity", targets: ["space_complexity"]), .executable(name: "leetcode_two_sum", targets: ["leetcode_two_sum"]), .executable(name: "array", targets: ["array"]), + .executable(name: "linked_list", targets: ["linked_list"]), ], targets: [ .target(name: "utils", path: "utils"), @@ -18,5 +19,6 @@ let package = Package( .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), .executableTarget(name: "leetcode_two_sum", path: "chapter_computational_complexity", sources: ["leetcode_two_sum.swift"]), .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), + .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), ] ) diff --git a/codes/swift/chapter_array_and_linkedlist/linked_list.swift b/codes/swift/chapter_array_and_linkedlist/linked_list.swift new file mode 100644 index 000000000..06837f750 --- /dev/null +++ b/codes/swift/chapter_array_and_linkedlist/linked_list.swift @@ -0,0 +1,91 @@ +/** + * File: linked_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 在链表的结点 n0 之后插入结点 P */ +func insert(n0: ListNode, P: ListNode) { + let n1 = n0.next + n0.next = P + P.next = n1 +} + +/* 删除链表的结点 n0 之后的首个结点 */ +func remove(n0: ListNode) { + if n0.next == nil { + return + } + // n0 -> P -> n1 + let P = n0.next + let n1 = P?.next + n0.next = n1 + P?.next = nil +} + +/* 访问链表中索引为 index 的结点 */ +func access(head: ListNode, index: Int) -> ListNode? { + var head: ListNode? = head + for _ in 0 ..< index { + head = head?.next + if head == nil { + return nil + } + } + return head +} + +/* 在链表中查找值为 target 的首个结点 */ +func find(head: ListNode, target: Int) -> Int { + var head: ListNode? = head + var index = 0 + while head != nil { + if head?.val == target { + return index + } + head = head?.next + index += 1 + } + return -1 +} + +@main +enum LinkedList { + /* Driver Code */ + static func main() { + /* 初始化链表 */ + // 初始化各个结点 + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // 构建引用指向 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + print("初始化的链表为") + PrintUtil.printLinkedList(head: n0) + + /* 插入结点 */ + insert(n0: n0, P: ListNode(x: 0)) + print("插入结点后的链表为") + PrintUtil.printLinkedList(head: n0) + + /* 删除结点 */ + remove(n0: n0) + print("删除结点后的链表为") + PrintUtil.printLinkedList(head: n0) + + /* 访问结点 */ + let node = access(head: n0, index: 3) + print("链表中索引 3 处的结点的值 = \(node!.val)") + + /* 查找结点 */ + let index = find(head: n0, target: 2) + print("链表中值为 2 的结点的索引 = \(index)") + } +} diff --git a/codes/swift/utils/PrintUtil.swift b/codes/swift/utils/PrintUtil.swift index f2b54693d..cbeab1225 100644 --- a/codes/swift/utils/PrintUtil.swift +++ b/codes/swift/utils/PrintUtil.swift @@ -15,6 +15,16 @@ public enum PrintUtil { } } + public static func printLinkedList(head: ListNode) { + var head: ListNode? = head + var list: [String] = [] + while head != nil { + list.append("\(head!.val)") + head = head?.next + } + print(list.joined(separator: " -> ")) + } + public static func printTree(root: TreeNode?) { printTree(root: root, prev: nil, isLeft: false) } diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index aa9c93ec7..7aa2754ca 100644 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -115,7 +115,15 @@ comments: true === "Swift" ```swift title="" + /* 链表结点类 */ + class ListNode { + var val: Int // 结点值 + var next: ListNode? // 指向下一结点的指针(引用) + init(x: Int) { // 构造函数 + val = x + } + } ``` **尾结点指向什么?** 我们一般将链表的最后一个结点称为「尾结点」,其指向的是「空」,在 Java / C++ / Python 中分别记为 `null` / `nullptr` / `None` 。在不引起歧义下,本书都使用 `null` 来表示空。 @@ -255,7 +263,18 @@ comments: true === "Swift" ```swift title="linked_list.swift" - + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各个结点 + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // 构建引用指向 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 ``` ## 链表优点 @@ -425,7 +444,24 @@ comments: true === "Swift" ```swift title="linked_list.swift" + /* 在链表的结点 n0 之后插入结点 P */ + func insert(n0: ListNode, P: ListNode) { + let n1 = n0.next + n0.next = P + P.next = n1 + } + /* 删除链表的结点 n0 之后的首个结点 */ + func remove(n0: ListNode) { + if n0.next == nil { + return + } + // n0 -> P -> n1 + let P = n0.next + let n1 = P?.next + n0.next = n1 + P?.next = nil + } ``` ## 链表缺点 @@ -541,7 +577,17 @@ comments: true === "Swift" ```swift title="linked_list.swift" - + /* 访问链表中索引为 index 的结点 */ + func access(head: ListNode, index: Int) -> ListNode? { + var head: ListNode? = head + for _ in 0 ..< index { + head = head?.next + if head == nil { + return nil + } + } + return head + } ``` **链表的内存占用多**。链表以结点为单位,每个结点除了保存值外,还需额外保存指针(引用)。这意味着同样数据量下,链表比数组需要占用更多内存空间。 @@ -674,7 +720,19 @@ comments: true === "Swift" ```swift title="linked_list.swift" - + /* 在链表中查找值为 target 的首个结点 */ + func find(head: ListNode, target: Int) -> Int { + var head: ListNode? = head + var index = 0 + while head != nil { + if head?.val == target { + return index + } + head = head?.next + index += 1 + } + return -1 + } ``` ## 常见链表类型 @@ -793,7 +851,16 @@ comments: true === "Swift" ```swift title="" + /* 双向链表结点类 */ + class ListNode { + var val: Int // 结点值 + var next: ListNode? // 指向后继结点的指针(引用) + var prev: ListNode? // 指向前驱结点的指针(引用) + init(x: Int) { // 构造函数 + val = x + } + } ``` ![linkedlist_common_types](linked_list.assets/linkedlist_common_types.png) From e8f7d8f8bab845eeca8082a0035cf01002739cfd Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Sun, 8 Jan 2023 20:30:09 +0800 Subject: [PATCH 11/21] Update .gitignore --- .gitignore | 4 +- codes/java/chapter_heap/my_heap.java | 115 --------------------------- 2 files changed, 1 insertion(+), 118 deletions(-) delete mode 100644 codes/java/chapter_heap/my_heap.java diff --git a/.gitignore b/.gitignore index e93873c95..c960a1bb3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Editor .vscode/ .idea/ +hello-algo.iml # mkdocs files site/ @@ -13,6 +14,3 @@ docs/overrides/ # python files __pycache__ - -# in-progress articles -docs/chapter_heap diff --git a/codes/java/chapter_heap/my_heap.java b/codes/java/chapter_heap/my_heap.java deleted file mode 100644 index da2dffc0a..000000000 --- a/codes/java/chapter_heap/my_heap.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * File: my_heap.java - * Created Time: 2023-01-07 - * Author: Krahets (krahets@163.com) - */ - -package chapter_heap; - -import include.*; -import java.util.*; - -class MaxHeap { - private List heap; - - public MaxHeap() { - heap = new ArrayList<>(); - } - - public MaxHeap(List nums) { - // 将元素拷贝至堆中 - heap = new ArrayList<>(nums); - // 堆化除叶结点外的其他所有结点 - for (int i = parent(size() - 1); i >= 0; i--) { - heapify(i); - } - } - - /* 获取左子结点 */ - private int left(int i) { - return 2 * i + 1; - } - - /* 获取右子结点 */ - private int right(int i) { - return 2 * i + 2; - } - - /* 获取父结点 */ - private int parent(int i) { - return (i - 1) / 2; - } - - /* 交换元素 */ - private void swap(int i, int j) { - int tmp = heap.get(i); - heap.set(i, j); - heap.set(j, tmp); - } - - public int size() { - return heap.size(); - } - - public boolean isEmpty() { - return size() == 0; - } - - /* 获取堆顶元素 */ - public int peek() { - return heap.get(0); - } - - /* 元素入堆 */ - public void push(int val) { - heap.add(val); - - // 从底至顶堆化 - int i = size(); - while (true) { - int p = parent(i); - if (p < 0 || heap.get(i) > heap.get(p)) - break; - swap(i, p); - i = p; - } - } - - /* 元素出堆 */ - public int poll() { - // 判空处理 - if (isEmpty()) - throw new EmptyStackException(); - // 交换根结点与右下角(即最后一个)结点 - swap(0, size() - 1); - // 删除结点 - int val = heap.remove(size() - 1); - // 从顶至底堆化 - heapify(0); - // 返回堆顶元素 - return val; - } - - /* 从结点 i 开始,从顶至底堆化 */ - private void heapify(int i) { - while (true) { - // 判断结点 i, l, r 中的最大结点,记为 ma ; - int l = left(i), r = right(i), ma = i; - if (heap.get(l) > heap.get(ma)) ma = l; - if (heap.get(r) > heap.get(ma)) ma = r; - // 若结点 i 最大,则无需继续堆化,跳出 - if (ma == i) break; - // 交换结点 i 与结点 max - swap(i, ma); - // 循环向下堆化 - i = ma; - } - } -} - - -public class my_heap { - public static void main(String[] args) { - - } -} From b73ac7bf4b0d3c21ece986a40b734d7f561cc7e4 Mon Sep 17 00:00:00 2001 From: reanon <793584285@qq.com> Date: Tue, 3 Jan 2023 14:39:31 +0800 Subject: [PATCH 12/21] style(go): fix go code style Make the classes and methods in the package private, in case misuse --- .../chapter_array_and_linkedlist/my_list.go | 24 ++++----- .../space_complexity.go | 32 ++++++------ codes/go/chapter_hashing/array_hash_map.go | 32 ++++++------ .../chapter_searching/hashing_search_test.go | 3 +- codes/go/chapter_sorting/merge_sort.go | 12 ++--- codes/go/chapter_sorting/quick_sort.go | 20 +++---- codes/go/chapter_sorting/quick_sort_test.go | 6 +-- .../go/chapter_stack_and_queue/array_queue.go | 38 +++++++------- .../go/chapter_stack_and_queue/array_stack.go | 36 ++++++------- .../go/chapter_stack_and_queue/deque_test.go | 30 +++++------ .../linkedlist_deque.go | 52 +++++++++---------- .../linkedlist_queue.go | 34 ++++++------ .../linkedlist_stack.go | 34 ++++++------ .../go/chapter_stack_and_queue/queue_test.go | 52 +++++++++---------- .../go/chapter_stack_and_queue/stack_test.go | 52 +++++++++---------- codes/go/chapter_tree/binary_search_tree.go | 24 ++++----- .../chapter_tree/binary_search_tree_test.go | 24 ++++----- codes/go/pkg/print_utils.go | 4 +- 18 files changed, 255 insertions(+), 254 deletions(-) diff --git a/codes/go/chapter_array_and_linkedlist/my_list.go b/codes/go/chapter_array_and_linkedlist/my_list.go index 8f13630be..134096d0a 100644 --- a/codes/go/chapter_array_and_linkedlist/my_list.go +++ b/codes/go/chapter_array_and_linkedlist/my_list.go @@ -5,7 +5,7 @@ package chapter_array_and_linkedlist /* 列表类简易实现 */ -type MyList struct { +type myList struct { numsCapacity int nums []int numsSize int @@ -13,8 +13,8 @@ type MyList struct { } /* 构造函数 */ -func newMyList() *MyList { - return &MyList{ +func newMyList() *myList { + return &myList{ numsCapacity: 10, // 列表容量 nums: make([]int, 10), // 数组(存储列表元素) numsSize: 0, // 列表长度(即当前元素数量) @@ -23,17 +23,17 @@ func newMyList() *MyList { } /* 获取列表长度(即当前元素数量) */ -func (l *MyList) size() int { +func (l *myList) size() int { return l.numsSize } /* 获取列表容量 */ -func (l *MyList) capacity() int { +func (l *myList) capacity() int { return l.numsCapacity } /* 访问元素 */ -func (l *MyList) get(index int) int { +func (l *myList) get(index int) int { // 索引如果越界则抛出异常,下同 if index >= l.numsSize { panic("索引越界") @@ -42,7 +42,7 @@ func (l *MyList) get(index int) int { } /* 更新元素 */ -func (l *MyList) set(num, index int) { +func (l *myList) set(num, index int) { if index >= l.numsSize { panic("索引越界") } @@ -50,7 +50,7 @@ func (l *MyList) set(num, index int) { } /* 尾部添加元素 */ -func (l *MyList) add(num int) { +func (l *myList) add(num int) { // 元素数量超出容量时,触发扩容机制 if l.numsSize == l.numsCapacity { l.extendCapacity() @@ -61,7 +61,7 @@ func (l *MyList) add(num int) { } /* 中间插入元素 */ -func (l *MyList) insert(num, index int) { +func (l *myList) insert(num, index int) { if index >= l.numsSize { panic("索引越界") } @@ -79,7 +79,7 @@ func (l *MyList) insert(num, index int) { } /* 删除元素 */ -func (l *MyList) remove(index int) int { +func (l *myList) remove(index int) int { if index >= l.numsSize { panic("索引越界") } @@ -95,7 +95,7 @@ func (l *MyList) remove(index int) int { } /* 列表扩容 */ -func (l *MyList) extendCapacity() { +func (l *myList) extendCapacity() { // 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...) // 更新列表容量 @@ -103,7 +103,7 @@ func (l *MyList) extendCapacity() { } /* 返回有效长度的列表 */ -func (l *MyList) toArray() []int { +func (l *myList) toArray() []int { // 仅转换有效长度范围内的列表元素 return l.nums[:l.numsSize] } diff --git a/codes/go/chapter_computational_complexity/space_complexity.go b/codes/go/chapter_computational_complexity/space_complexity.go index eb3ebec97..93f5b3380 100644 --- a/codes/go/chapter_computational_complexity/space_complexity.go +++ b/codes/go/chapter_computational_complexity/space_complexity.go @@ -9,31 +9,31 @@ import ( "strconv" ) -/* Node 结构体 */ -type Node struct { +/* 结构体 */ +type node struct { val int - next *Node + next *node } -/* TreeNode 二叉树 */ -type TreeNode struct { +/* treeNode 二叉树 */ +type treeNode struct { val int - left *TreeNode - right *TreeNode + left *treeNode + right *treeNode } -/* 创建 Node 结构体 */ -func newNode(val int) *Node { - return &Node{val: val} +/* 创建 node 结构体 */ +func newNode(val int) *node { + return &node{val: val} } -/* 创建 TreeNode 结构体 */ -func newTreeNode(val int) *TreeNode { - return &TreeNode{val: val} +/* 创建 treeNode 结构体 */ +func newTreeNode(val int) *treeNode { + return &treeNode{val: val} } /* 输出二叉树 */ -func printTree(root *TreeNode) { +func printTree(root *treeNode) { if root == nil { return } @@ -72,7 +72,7 @@ func spaceLinear(n int) { // 长度为 n 的数组占用 O(n) 空间 _ = make([]int, n) // 长度为 n 的列表占用 O(n) 空间 - var nodes []*Node + var nodes []*node for i := 0; i < n; i++ { nodes = append(nodes, newNode(i)) } @@ -112,7 +112,7 @@ func spaceQuadraticRecur(n int) int { } /* 指数阶(建立满二叉树) */ -func buildTree(n int) *TreeNode { +func buildTree(n int) *treeNode { if n == 0 { return nil } diff --git a/codes/go/chapter_hashing/array_hash_map.go b/codes/go/chapter_hashing/array_hash_map.go index a962b6a4d..5fb1480cc 100644 --- a/codes/go/chapter_hashing/array_hash_map.go +++ b/codes/go/chapter_hashing/array_hash_map.go @@ -7,30 +7,30 @@ package chapter_hashing import "fmt" /* 键值对 int->String */ -type Entry struct { +type entry struct { key int val string } /* 基于数组简易实现的哈希表 */ -type ArrayHashMap struct { - bucket []*Entry +type arrayHashMap struct { + bucket []*entry } -func newArrayHashMap() *ArrayHashMap { +func newArrayHashMap() *arrayHashMap { // 初始化一个长度为 100 的桶(数组) - bucket := make([]*Entry, 100) - return &ArrayHashMap{bucket: bucket} + bucket := make([]*entry, 100) + return &arrayHashMap{bucket: bucket} } /* 哈希函数 */ -func (a *ArrayHashMap) hashFunc(key int) int { +func (a *arrayHashMap) hashFunc(key int) int { index := key % 100 return index } /* 查询操作 */ -func (a *ArrayHashMap) get(key int) string { +func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) pair := a.bucket[index] if pair == nil { @@ -40,22 +40,22 @@ func (a *ArrayHashMap) get(key int) string { } /* 添加操作 */ -func (a *ArrayHashMap) put(key int, val string) { - pair := &Entry{key: key, val: val} +func (a *arrayHashMap) put(key int, val string) { + pair := &entry{key: key, val: val} index := a.hashFunc(key) a.bucket[index] = pair } /* 删除操作 */ -func (a *ArrayHashMap) remove(key int) { +func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) // 置为 nil ,代表删除 a.bucket[index] = nil } /* 获取所有键对 */ -func (a *ArrayHashMap) entrySet() []*Entry { - var pairs []*Entry +func (a *arrayHashMap) entrySet() []*entry { + var pairs []*entry for _, pair := range a.bucket { if pair != nil { pairs = append(pairs, pair) @@ -65,7 +65,7 @@ func (a *ArrayHashMap) entrySet() []*Entry { } /* 获取所有键 */ -func (a *ArrayHashMap) keySet() []int { +func (a *arrayHashMap) keySet() []int { var keys []int for _, pair := range a.bucket { if pair != nil { @@ -76,7 +76,7 @@ func (a *ArrayHashMap) keySet() []int { } /* 获取所有值 */ -func (a *ArrayHashMap) valueSet() []string { +func (a *arrayHashMap) valueSet() []string { var values []string for _, pair := range a.bucket { if pair != nil { @@ -87,7 +87,7 @@ func (a *ArrayHashMap) valueSet() []string { } /* 打印哈希表 */ -func (a *ArrayHashMap) print() { +func (a *arrayHashMap) print() { for _, pair := range a.bucket { if pair != nil { fmt.Println(pair.key, "->", pair.val) diff --git a/codes/go/chapter_searching/hashing_search_test.go b/codes/go/chapter_searching/hashing_search_test.go index 4bccd038f..18cf5c651 100644 --- a/codes/go/chapter_searching/hashing_search_test.go +++ b/codes/go/chapter_searching/hashing_search_test.go @@ -6,8 +6,9 @@ package chapter_searching import ( "fmt" - . "github.com/krahets/hello-algo/pkg" "testing" + + . "github.com/krahets/hello-algo/pkg" ) func TestHashingSearch(t *testing.T) { diff --git a/codes/go/chapter_sorting/merge_sort.go b/codes/go/chapter_sorting/merge_sort.go index 1b3a3932d..43aff01a7 100644 --- a/codes/go/chapter_sorting/merge_sort.go +++ b/codes/go/chapter_sorting/merge_sort.go @@ -8,25 +8,25 @@ package chapter_sorting // 左子数组区间 [left, mid] // 右子数组区间 [mid + 1, right] func merge(nums []int, left, mid, right int) { - // 初始化辅助数组 借助 copy模块 + // 初始化辅助数组 借助 copy 模块 tmp := make([]int, right-left+1) for i := left; i <= right; i++ { tmp[i-left] = nums[i] } // 左子数组的起始索引和结束索引 - left_start, left_end := left-left, mid-left + leftStart, leftEnd := left-left, mid-left // 右子数组的起始索引和结束索引 - right_start, right_end := mid+1-left, right-left + rightStart, rightEnd := mid+1-left, right-left // i, j 分别指向左子数组、右子数组的首元素 - i, j := left_start, right_start + i, j := leftStart, rightStart // 通过覆盖原数组 nums 来合并左子数组和右子数组 for k := left; k <= right; k++ { // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > left_end { + if i > leftEnd { nums[k] = tmp[j] j++ // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if j > right_end || tmp[i] <= tmp[j] { + } else if j > rightEnd || tmp[i] <= tmp[j] { nums[k] = tmp[i] i++ // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ diff --git a/codes/go/chapter_sorting/quick_sort.go b/codes/go/chapter_sorting/quick_sort.go index 88aa14264..853bce452 100644 --- a/codes/go/chapter_sorting/quick_sort.go +++ b/codes/go/chapter_sorting/quick_sort.go @@ -5,16 +5,16 @@ package chapter_sorting // 快速排序 -type QuickSort struct{} +type quickSort struct{} // 快速排序(中位基准数优化) -type QuickSortMedian struct{} +type quickSortMedian struct{} // 快速排序(尾递归优化) -type QuickSortTailCall struct{} +type quickSortTailCall struct{} /* 哨兵划分 */ -func (q *QuickSort) partition(nums []int, left, right int) int { +func (q *quickSort) partition(nums []int, left, right int) int { // 以 nums[left] 作为基准数 i, j := left, right for i < j { @@ -33,7 +33,7 @@ func (q *QuickSort) partition(nums []int, left, right int) int { } /* 快速排序 */ -func (q *QuickSort) quickSort(nums []int, left, right int) { +func (q *quickSort) quickSort(nums []int, left, right int) { // 子数组长度为 1 时终止递归 if left >= right { return @@ -46,7 +46,7 @@ func (q *QuickSort) quickSort(nums []int, left, right int) { } /* 选取三个元素的中位数 */ -func (q *QuickSortMedian) medianThree(nums []int, left, mid, right int) int { +func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { if (nums[left] > nums[mid]) != (nums[left] > nums[right]) { return left } else if (nums[mid] < nums[left]) != (nums[mid] > nums[right]) { @@ -56,7 +56,7 @@ func (q *QuickSortMedian) medianThree(nums []int, left, mid, right int) int { } /* 哨兵划分(三数取中值)*/ -func (q *QuickSortMedian) partition(nums []int, left, right int) int { +func (q *quickSortMedian) partition(nums []int, left, right int) int { // 以 nums[left] 作为基准数 med := q.medianThree(nums, left, (left+right)/2, right) // 将中位数交换至数组最左端 @@ -79,7 +79,7 @@ func (q *QuickSortMedian) partition(nums []int, left, right int) int { } /* 快速排序 */ -func (q *QuickSortMedian) quickSort(nums []int, left, right int) { +func (q *quickSortMedian) quickSort(nums []int, left, right int) { // 子数组长度为 1 时终止递归 if left >= right { return @@ -92,7 +92,7 @@ func (q *QuickSortMedian) quickSort(nums []int, left, right int) { } /* 哨兵划分 */ -func (q *QuickSortTailCall) partition(nums []int, left, right int) int { +func (q *quickSortTailCall) partition(nums []int, left, right int) int { // 以 nums[left] 作为基准数 i, j := left, right for i < j { @@ -111,7 +111,7 @@ func (q *QuickSortTailCall) partition(nums []int, left, right int) int { } /* 快速排序(尾递归优化)*/ -func (q *QuickSortTailCall) quickSort(nums []int, left, right int) { +func (q *quickSortTailCall) quickSort(nums []int, left, right int) { // 子数组长度为 1 时终止 for left < right { // 哨兵划分操作 diff --git a/codes/go/chapter_sorting/quick_sort_test.go b/codes/go/chapter_sorting/quick_sort_test.go index 86ae0115a..d780663e1 100644 --- a/codes/go/chapter_sorting/quick_sort_test.go +++ b/codes/go/chapter_sorting/quick_sort_test.go @@ -11,7 +11,7 @@ import ( // 快速排序 func TestQuickSort(t *testing.T) { - q := QuickSort{} + q := quickSort{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序完成后 nums = ", nums) @@ -19,7 +19,7 @@ func TestQuickSort(t *testing.T) { // 快速排序(中位基准数优化) func TestQuickSortMedian(t *testing.T) { - q := QuickSortMedian{} + q := quickSortMedian{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序(中位基准数优化)完成后 nums = ", nums) @@ -27,7 +27,7 @@ func TestQuickSortMedian(t *testing.T) { // 快速排序(尾递归优化) func TestQuickSortTailCall(t *testing.T) { - q := QuickSortTailCall{} + q := quickSortTailCall{} nums := []int{4, 1, 3, 1, 5, 2} q.quickSort(nums, 0, len(nums)-1) fmt.Println("快速排序(尾递归优化)完成后 nums = ", nums) diff --git a/codes/go/chapter_stack_and_queue/array_queue.go b/codes/go/chapter_stack_and_queue/array_queue.go index 863768ba8..7d0367df8 100644 --- a/codes/go/chapter_stack_and_queue/array_queue.go +++ b/codes/go/chapter_stack_and_queue/array_queue.go @@ -5,16 +5,16 @@ package chapter_stack_and_queue /* 基于环形数组实现的队列 */ -type ArrayQueue struct { +type arrayQueue struct { data []int // 用于存储队列元素的数组 capacity int // 队列容量(即最多容量的元素个数) front int // 头指针,指向队首 rear int // 尾指针,指向队尾 + 1 } -// NewArrayQueue 基于环形数组实现的队列 -func NewArrayQueue(capacity int) *ArrayQueue { - return &ArrayQueue{ +// newArrayQueue 基于环形数组实现的队列 +func newArrayQueue(capacity int) *arrayQueue { + return &arrayQueue{ data: make([]int, capacity), capacity: capacity, front: 0, @@ -22,21 +22,21 @@ func NewArrayQueue(capacity int) *ArrayQueue { } } -// Size 获取队列的长度 -func (q *ArrayQueue) Size() int { +// size 获取队列的长度 +func (q *arrayQueue) size() int { size := (q.capacity + q.rear - q.front) % q.capacity return size } -// IsEmpty 判断队列是否为空 -func (q *ArrayQueue) IsEmpty() bool { +// isEmpty 判断队列是否为空 +func (q *arrayQueue) isEmpty() bool { return q.rear-q.front == 0 } -// Offer 入队 -func (q *ArrayQueue) Offer(v int) { +// offer 入队 +func (q *arrayQueue) offer(v int) { // 当 rear == capacity 表示队列已满 - if q.Size() == q.capacity { + if q.size() == q.capacity { return } // 尾结点后添加 @@ -45,9 +45,9 @@ func (q *ArrayQueue) Offer(v int) { q.rear = (q.rear + 1) % q.capacity } -// Poll 出队 -func (q *ArrayQueue) Poll() any { - if q.IsEmpty() { +// poll 出队 +func (q *arrayQueue) poll() any { + if q.isEmpty() { return nil } v := q.data[q.front] @@ -56,9 +56,9 @@ func (q *ArrayQueue) Poll() any { return v } -// Peek 访问队首元素 -func (q *ArrayQueue) Peek() any { - if q.IsEmpty() { +// peek 访问队首元素 +func (q *arrayQueue) peek() any { + if q.isEmpty() { return nil } v := q.data[q.front] @@ -66,6 +66,6 @@ func (q *ArrayQueue) Peek() any { } // 获取 Slice 用于打印 -func (s *ArrayQueue) toSlice() []int { - return s.data[s.front:s.rear] +func (q *arrayQueue) toSlice() []int { + return q.data[q.front:q.rear] } diff --git a/codes/go/chapter_stack_and_queue/array_stack.go b/codes/go/chapter_stack_and_queue/array_stack.go index dca97404d..ef32ebe4c 100644 --- a/codes/go/chapter_stack_and_queue/array_stack.go +++ b/codes/go/chapter_stack_and_queue/array_stack.go @@ -5,47 +5,47 @@ package chapter_stack_and_queue /* 基于数组实现的栈 */ -type ArrayStack struct { +type arrayStack struct { data []int // 数据 } -func NewArrayStack() *ArrayStack { - return &ArrayStack{ +func newArrayStack() *arrayStack { + return &arrayStack{ // 设置栈的长度为 0,容量为 16 data: make([]int, 0, 16), } } -// Size 栈的长度 -func (s *ArrayStack) Size() int { +// size 栈的长度 +func (s *arrayStack) size() int { return len(s.data) } -// IsEmpty 栈是否为空 -func (s *ArrayStack) IsEmpty() bool { - return s.Size() == 0 +// isEmpty 栈是否为空 +func (s *arrayStack) isEmpty() bool { + return s.size() == 0 } -// Push 入栈 -func (s *ArrayStack) Push(v int) { +// push 入栈 +func (s *arrayStack) push(v int) { // 切片会自动扩容 s.data = append(s.data, v) } -// Pop 出栈 -func (s *ArrayStack) Pop() any { +// pop 出栈 +func (s *arrayStack) pop() any { // 弹出栈前,先判断是否为空 - if s.IsEmpty() { + if s.isEmpty() { return nil } - val := s.Peek() + val := s.peek() s.data = s.data[:len(s.data)-1] return val } -// Peek 获取栈顶元素 -func (s *ArrayStack) Peek() any { - if s.IsEmpty() { +// peek 获取栈顶元素 +func (s *arrayStack) peek() any { + if s.isEmpty() { return nil } val := s.data[len(s.data)-1] @@ -53,6 +53,6 @@ func (s *ArrayStack) Peek() any { } // 获取 Slice 用于打印 -func (s *ArrayStack) toSlice() []int { +func (s *arrayStack) toSlice() []int { return s.data } diff --git a/codes/go/chapter_stack_and_queue/deque_test.go b/codes/go/chapter_stack_and_queue/deque_test.go index 647ac642e..4c0f2f533 100644 --- a/codes/go/chapter_stack_and_queue/deque_test.go +++ b/codes/go/chapter_stack_and_queue/deque_test.go @@ -51,48 +51,48 @@ func TestDeque(t *testing.T) { func TestLinkedListDeque(t *testing.T) { // 初始化队列 - deque := NewLinkedListDeque() + deque := newLinkedListDeque() // 元素入队 - deque.OfferLast(2) - deque.OfferLast(5) - deque.OfferLast(4) - deque.OfferFirst(3) - deque.OfferFirst(1) + deque.offerLast(2) + deque.offerLast(5) + deque.offerLast(4) + deque.offerFirst(3) + deque.offerFirst(1) fmt.Print("队列 deque = ") PrintList(deque.toList()) // 访问队首元素 - front := deque.PeekFirst() + front := deque.peekFirst() fmt.Println("队首元素 front =", front) - rear := deque.PeekLast() + rear := deque.peekLast() fmt.Println("队尾元素 rear =", rear) // 元素出队 - pollFirst := deque.PollFirst() + pollFirst := deque.pollFirst() fmt.Print("队首出队元素 pollFirst = ", pollFirst, ",队首出队后 deque = ") PrintList(deque.toList()) - pollLast := deque.PollLast() + pollLast := deque.pollLast() fmt.Print("队尾出队元素 pollLast = ", pollLast, ",队尾出队后 deque = ") PrintList(deque.toList()) // 获取队的长度 - size := deque.Size() + size := deque.size() fmt.Println("队的长度 size =", size) // 判断是否为空 - isEmpty := deque.IsEmpty() + isEmpty := deque.isEmpty() fmt.Println("队是否为空 =", isEmpty) } // BenchmarkArrayQueue 67.92 ns/op in Mac M1 Pro func BenchmarkLinkedListDeque(b *testing.B) { - stack := NewLinkedListDeque() + stack := newLinkedListDeque() // use b.N for looping for i := 0; i < b.N; i++ { - stack.OfferLast(777) + stack.offerLast(777) } for i := 0; i < b.N; i++ { - stack.PollFirst() + stack.pollFirst() } } diff --git a/codes/go/chapter_stack_and_queue/linkedlist_deque.go b/codes/go/chapter_stack_and_queue/linkedlist_deque.go index 019aa778b..b1db77ca6 100644 --- a/codes/go/chapter_stack_and_queue/linkedlist_deque.go +++ b/codes/go/chapter_stack_and_queue/linkedlist_deque.go @@ -8,31 +8,31 @@ import ( "container/list" ) -// LinkedListDeque 基于链表实现的双端队列, 使用内置包 list 来实现栈 -type LinkedListDeque struct { +// linkedListDeque 基于链表实现的双端队列, 使用内置包 list 来实现栈 +type linkedListDeque struct { data *list.List } -// NewLinkedListDeque 初始化双端队列 -func NewLinkedListDeque() *LinkedListDeque { - return &LinkedListDeque{ +// newLinkedListDeque 初始化双端队列 +func newLinkedListDeque() *linkedListDeque { + return &linkedListDeque{ data: list.New(), } } -// OfferFirst 队首元素入队 -func (s *LinkedListDeque) OfferFirst(value any) { +// offerFirst 队首元素入队 +func (s *linkedListDeque) offerFirst(value any) { s.data.PushFront(value) } -// OfferLast 队尾元素入队 -func (s *LinkedListDeque) OfferLast(value any) { +// offerLast 队尾元素入队 +func (s *linkedListDeque) offerLast(value any) { s.data.PushBack(value) } -// PollFirst 队首元素出队 -func (s *LinkedListDeque) PollFirst() any { - if s.IsEmpty() { +// pollFirst 队首元素出队 +func (s *linkedListDeque) pollFirst() any { + if s.isEmpty() { return nil } e := s.data.Front() @@ -40,9 +40,9 @@ func (s *LinkedListDeque) PollFirst() any { return e.Value } -// PollLast 队尾元素出队 -func (s *LinkedListDeque) PollLast() any { - if s.IsEmpty() { +// pollLast 队尾元素出队 +func (s *linkedListDeque) pollLast() any { + if s.isEmpty() { return nil } e := s.data.Back() @@ -50,35 +50,35 @@ func (s *LinkedListDeque) PollLast() any { return e.Value } -// PeekFirst 访问队首元素 -func (s *LinkedListDeque) PeekFirst() any { - if s.IsEmpty() { +// peekFirst 访问队首元素 +func (s *linkedListDeque) peekFirst() any { + if s.isEmpty() { return nil } e := s.data.Front() return e.Value } -// PeekLast 访问队尾元素 -func (s *LinkedListDeque) PeekLast() any { - if s.IsEmpty() { +// peekLast 访问队尾元素 +func (s *linkedListDeque) peekLast() any { + if s.isEmpty() { return nil } e := s.data.Back() return e.Value } -// Size 获取队列的长度 -func (s *LinkedListDeque) Size() int { +// size 获取队列的长度 +func (s *linkedListDeque) size() int { return s.data.Len() } -// IsEmpty 判断队列是否为空 -func (s *LinkedListDeque) IsEmpty() bool { +// isEmpty 判断队列是否为空 +func (s *linkedListDeque) isEmpty() bool { return s.data.Len() == 0 } // 获取 List 用于打印 -func (s *LinkedListDeque) toList() *list.List { +func (s *linkedListDeque) toList() *list.List { return s.data } diff --git a/codes/go/chapter_stack_and_queue/linkedlist_queue.go b/codes/go/chapter_stack_and_queue/linkedlist_queue.go index d30461805..f5d164a82 100644 --- a/codes/go/chapter_stack_and_queue/linkedlist_queue.go +++ b/codes/go/chapter_stack_and_queue/linkedlist_queue.go @@ -9,26 +9,26 @@ import ( ) /* 基于链表实现的队列 */ -type LinkedListQueue struct { +type linkedListQueue struct { // 使用内置包 list 来实现队列 data *list.List } -// NewLinkedListQueue 初始化链表 -func NewLinkedListQueue() *LinkedListQueue { - return &LinkedListQueue{ +// newLinkedListQueue 初始化链表 +func newLinkedListQueue() *linkedListQueue { + return &linkedListQueue{ data: list.New(), } } -// Offer 入队 -func (s *LinkedListQueue) Offer(value any) { +// offer 入队 +func (s *linkedListQueue) offer(value any) { s.data.PushBack(value) } -// Poll 出队 -func (s *LinkedListQueue) Poll() any { - if s.IsEmpty() { +// poll 出队 +func (s *linkedListQueue) poll() any { + if s.isEmpty() { return nil } e := s.data.Front() @@ -36,26 +36,26 @@ func (s *LinkedListQueue) Poll() any { return e.Value } -// Peek 访问队首元素 -func (s *LinkedListQueue) Peek() any { - if s.IsEmpty() { +// peek 访问队首元素 +func (s *linkedListQueue) peek() any { + if s.isEmpty() { return nil } e := s.data.Front() return e.Value } -// Size 获取队列的长度 -func (s *LinkedListQueue) Size() int { +// size 获取队列的长度 +func (s *linkedListQueue) size() int { return s.data.Len() } -// IsEmpty 判断队列是否为空 -func (s *LinkedListQueue) IsEmpty() bool { +// isEmpty 判断队列是否为空 +func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } // 获取 List 用于打印 -func (s *LinkedListQueue) toList() *list.List { +func (s *linkedListQueue) toList() *list.List { return s.data } diff --git a/codes/go/chapter_stack_and_queue/linkedlist_stack.go b/codes/go/chapter_stack_and_queue/linkedlist_stack.go index ab8a06284..8509c2b83 100644 --- a/codes/go/chapter_stack_and_queue/linkedlist_stack.go +++ b/codes/go/chapter_stack_and_queue/linkedlist_stack.go @@ -9,26 +9,26 @@ import ( ) /* 基于链表实现的栈 */ -type LinkedListStack struct { +type linkedListStack struct { // 使用内置包 list 来实现栈 data *list.List } -// NewLinkedListStack 初始化链表 -func NewLinkedListStack() *LinkedListStack { - return &LinkedListStack{ +// newLinkedListStack 初始化链表 +func newLinkedListStack() *linkedListStack { + return &linkedListStack{ data: list.New(), } } -// Push 入栈 -func (s *LinkedListStack) Push(value int) { +// push 入栈 +func (s *linkedListStack) push(value int) { s.data.PushBack(value) } -// Pop 出栈 -func (s *LinkedListStack) Pop() any { - if s.IsEmpty() { +// pop 出栈 +func (s *linkedListStack) pop() any { + if s.isEmpty() { return nil } e := s.data.Back() @@ -36,26 +36,26 @@ func (s *LinkedListStack) Pop() any { return e.Value } -// Peek 访问栈顶元素 -func (s *LinkedListStack) Peek() any { - if s.IsEmpty() { +// peek 访问栈顶元素 +func (s *linkedListStack) peek() any { + if s.isEmpty() { return nil } e := s.data.Back() return e.Value } -// Size 获取栈的长度 -func (s *LinkedListStack) Size() int { +// size 获取栈的长度 +func (s *linkedListStack) size() int { return s.data.Len() } -// IsEmpty 判断栈是否为空 -func (s *LinkedListStack) IsEmpty() bool { +// isEmpty 判断栈是否为空 +func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } // 获取 List 用于打印 -func (s *LinkedListStack) toList() *list.List { +func (s *linkedListStack) toList() *list.List { return s.data } diff --git a/codes/go/chapter_stack_and_queue/queue_test.go b/codes/go/chapter_stack_and_queue/queue_test.go index 368becbc7..b9ec79df2 100644 --- a/codes/go/chapter_stack_and_queue/queue_test.go +++ b/codes/go/chapter_stack_and_queue/queue_test.go @@ -48,87 +48,87 @@ func TestQueue(t *testing.T) { func TestArrayQueue(t *testing.T) { // 初始化队列,使用队列的通用接口 capacity := 10 - queue := NewArrayQueue(capacity) + queue := newArrayQueue(capacity) // 元素入队 - queue.Offer(1) - queue.Offer(3) - queue.Offer(2) - queue.Offer(5) - queue.Offer(4) + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) fmt.Print("队列 queue = ") PrintSlice(queue.toSlice()) // 访问队首元素 - peek := queue.Peek() + peek := queue.peek() fmt.Println("队首元素 peek =", peek) // 元素出队 - poll := queue.Poll() + poll := queue.poll() fmt.Print("出队元素 poll = ", poll, ", 出队后 queue = ") PrintSlice(queue.toSlice()) // 获取队的长度 - size := queue.Size() + size := queue.size() fmt.Println("队的长度 size =", size) // 判断是否为空 - isEmpty := queue.IsEmpty() + isEmpty := queue.isEmpty() fmt.Println("队是否为空 =", isEmpty) } func TestLinkedListQueue(t *testing.T) { // 初始化队 - queue := NewLinkedListQueue() + queue := newLinkedListQueue() // 元素入队 - queue.Offer(1) - queue.Offer(3) - queue.Offer(2) - queue.Offer(5) - queue.Offer(4) + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) fmt.Print("队列 queue = ") PrintList(queue.toList()) // 访问队首元素 - peek := queue.Peek() + peek := queue.peek() fmt.Println("队首元素 peek =", peek) // 元素出队 - poll := queue.Poll() + poll := queue.poll() fmt.Print("出队元素 poll = ", poll, ", 出队后 queue = ") PrintList(queue.toList()) // 获取队的长度 - size := queue.Size() + size := queue.size() fmt.Println("队的长度 size =", size) // 判断是否为空 - isEmpty := queue.IsEmpty() + isEmpty := queue.isEmpty() fmt.Println("队是否为空 =", isEmpty) } // BenchmarkArrayQueue 8 ns/op in Mac M1 Pro func BenchmarkArrayQueue(b *testing.B) { capacity := 1000 - stack := NewArrayQueue(capacity) + stack := newArrayQueue(capacity) // use b.N for looping for i := 0; i < b.N; i++ { - stack.Offer(777) + stack.offer(777) } for i := 0; i < b.N; i++ { - stack.Poll() + stack.poll() } } // BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro func BenchmarkLinkedQueue(b *testing.B) { - stack := NewLinkedListQueue() + stack := newLinkedListQueue() // use b.N for looping for i := 0; i < b.N; i++ { - stack.Offer(777) + stack.offer(777) } for i := 0; i < b.N; i++ { - stack.Poll() + stack.poll() } } diff --git a/codes/go/chapter_stack_and_queue/stack_test.go b/codes/go/chapter_stack_and_queue/stack_test.go index 9dc97e697..a4cf338b7 100644 --- a/codes/go/chapter_stack_and_queue/stack_test.go +++ b/codes/go/chapter_stack_and_queue/stack_test.go @@ -46,85 +46,85 @@ func TestStack(t *testing.T) { func TestArrayStack(t *testing.T) { // 初始化栈, 使用接口承接 - stack := NewArrayStack() + stack := newArrayStack() // 元素入栈 - stack.Push(1) - stack.Push(3) - stack.Push(2) - stack.Push(5) - stack.Push(4) + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) fmt.Print("栈 stack = ") PrintSlice(stack.toSlice()) // 访问栈顶元素 - peek := stack.Peek() + peek := stack.peek() fmt.Println("栈顶元素 peek =", peek) // 元素出栈 - pop := stack.Pop() + pop := stack.pop() fmt.Print("出栈元素 pop = ", pop, ", 出栈后 stack = ") PrintSlice(stack.toSlice()) // 获取栈的长度 - size := stack.Size() + size := stack.size() fmt.Println("栈的长度 size =", size) // 判断是否为空 - isEmpty := stack.IsEmpty() + isEmpty := stack.isEmpty() fmt.Println("栈是否为空 =", isEmpty) } func TestLinkedListStack(t *testing.T) { // 初始化栈 - stack := NewLinkedListStack() + stack := newLinkedListStack() // 元素入栈 - stack.Push(1) - stack.Push(3) - stack.Push(2) - stack.Push(5) - stack.Push(4) + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) fmt.Print("栈 stack = ") PrintList(stack.toList()) // 访问栈顶元素 - peek := stack.Peek() + peek := stack.peek() fmt.Println("栈顶元素 peek =", peek) // 元素出栈 - pop := stack.Pop() + pop := stack.pop() fmt.Print("出栈元素 pop = ", pop, ", 出栈后 stack = ") PrintList(stack.toList()) // 获取栈的长度 - size := stack.Size() + size := stack.size() fmt.Println("栈的长度 size =", size) // 判断是否为空 - isEmpty := stack.IsEmpty() + isEmpty := stack.isEmpty() fmt.Println("栈是否为空 =", isEmpty) } // BenchmarkArrayStack 8 ns/op in Mac M1 Pro func BenchmarkArrayStack(b *testing.B) { - stack := NewArrayStack() + stack := newArrayStack() // use b.N for looping for i := 0; i < b.N; i++ { - stack.Push(777) + stack.push(777) } for i := 0; i < b.N; i++ { - stack.Pop() + stack.pop() } } // BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro func BenchmarkLinkedListStack(b *testing.B) { - stack := NewLinkedListStack() + stack := newLinkedListStack() // use b.N for looping for i := 0; i < b.N; i++ { - stack.Push(777) + stack.push(777) } for i := 0; i < b.N; i++ { - stack.Pop() + stack.pop() } } diff --git a/codes/go/chapter_tree/binary_search_tree.go b/codes/go/chapter_tree/binary_search_tree.go index c8fc5d62c..61ed400d5 100644 --- a/codes/go/chapter_tree/binary_search_tree.go +++ b/codes/go/chapter_tree/binary_search_tree.go @@ -10,26 +10,26 @@ import ( . "github.com/krahets/hello-algo/pkg" ) -type BinarySearchTree struct { +type binarySearchTree struct { root *TreeNode } -func NewBinarySearchTree(nums []int) *BinarySearchTree { +func newBinarySearchTree(nums []int) *binarySearchTree { // sorting array sort.Ints(nums) root := buildBinarySearchTree(nums, 0, len(nums)-1) - return &BinarySearchTree{ + return &binarySearchTree{ root: root, } } /* 获取根结点 */ -func (bst *BinarySearchTree) GetRoot() *TreeNode { +func (bst *binarySearchTree) getRoot() *TreeNode { return bst.root } /* 获取中序遍历的下一个结点 */ -func (bst *BinarySearchTree) GetInOrderNext(node *TreeNode) *TreeNode { +func (bst *binarySearchTree) getInOrderNext(node *TreeNode) *TreeNode { if node == nil { return node } @@ -41,7 +41,7 @@ func (bst *BinarySearchTree) GetInOrderNext(node *TreeNode) *TreeNode { } /* 查找结点 */ -func (bst *BinarySearchTree) Search(num int) *TreeNode { +func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root // 循环查找,越过叶结点后跳出 for node != nil { @@ -61,7 +61,7 @@ func (bst *BinarySearchTree) Search(num int) *TreeNode { } /* 插入结点 */ -func (bst *BinarySearchTree) Insert(num int) *TreeNode { +func (bst *binarySearchTree) insert(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { @@ -92,7 +92,7 @@ func (bst *BinarySearchTree) Insert(num int) *TreeNode { } /* 删除结点 */ -func (bst *BinarySearchTree) Remove(num int) *TreeNode { +func (bst *binarySearchTree) remove(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { @@ -136,10 +136,10 @@ func (bst *BinarySearchTree) Remove(num int) *TreeNode { // 子结点数为 2 } else { // 获取中序遍历中待删除结点 cur 的下一个结点 - next := bst.GetInOrderNext(cur) + next := bst.getInOrderNext(cur) temp := next.Val // 递归删除结点 next - bst.Remove(next.Val) + bst.remove(next.Val) // 将 next 的值复制给 cur cur.Val = temp } @@ -160,7 +160,7 @@ func buildBinarySearchTree(nums []int, left, right int) *TreeNode { return root } -// Print binary search tree -func (bst *BinarySearchTree) Print() { +// print binary search tree +func (bst *binarySearchTree) print() { PrintTree(bst.root) } diff --git a/codes/go/chapter_tree/binary_search_tree_test.go b/codes/go/chapter_tree/binary_search_tree_test.go index cdf7d8d48..877f1b541 100644 --- a/codes/go/chapter_tree/binary_search_tree_test.go +++ b/codes/go/chapter_tree/binary_search_tree_test.go @@ -11,31 +11,31 @@ import ( func TestBinarySearchTree(t *testing.T) { nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - bst := NewBinarySearchTree(nums) + bst := newBinarySearchTree(nums) fmt.Println("\n初始化的二叉树为:") - bst.Print() + bst.print() // 获取根结点 - node := bst.GetRoot() + node := bst.getRoot() fmt.Println("\n二叉树的根结点为:", node.Val) // 查找结点 - node = bst.Search(5) + node = bst.search(5) fmt.Println("\n查找到的结点对象为", node, ",结点值 =", node.Val) // 插入结点 - node = bst.Insert(16) + node = bst.insert(16) fmt.Println("\n插入结点后 16 的二叉树为:") - bst.Print() + bst.print() // 删除结点 - bst.Remove(1) + bst.remove(1) fmt.Println("\n删除结点 1 后的二叉树为:") - bst.Print() - bst.Remove(2) + bst.print() + bst.remove(2) fmt.Println("\n删除结点 2 后的二叉树为:") - bst.Print() - bst.Remove(4) + bst.print() + bst.remove(4) fmt.Println("\n删除结点 4 后的二叉树为:") - bst.Print() + bst.print() } diff --git a/codes/go/pkg/print_utils.go b/codes/go/pkg/print_utils.go index a42d48b2f..575ab6b94 100644 --- a/codes/go/pkg/print_utils.go +++ b/codes/go/pkg/print_utils.go @@ -76,7 +76,7 @@ func printTreeHelper(root *TreeNode, prev *trunk, isLeft bool) { printTreeHelper(root.Left, trunk, false) } -// trunk Help to Print tree structure +// trunk Help to print tree structure type trunk struct { prev *trunk str string @@ -103,4 +103,4 @@ func PrintMap[K comparable, V any](m map[K]V) { for key, value := range m { fmt.Println(key, "->", value) } -} \ No newline at end of file +} From 33e2c4f4d31450ac03b201746bca3c3248addcc5 Mon Sep 17 00:00:00 2001 From: reanon <793584285@qq.com> Date: Sun, 8 Jan 2023 20:00:36 +0800 Subject: [PATCH 13/21] fix(tree): fix ArrToTree in go code --- codes/go/chapter_tree/binary_tree_bfs_test.go | 2 +- codes/go/chapter_tree/binary_tree_dfs_test.go | 2 +- codes/go/pkg/tree_node.go | 17 +++++++++++------ codes/go/pkg/tree_node_test.go | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/codes/go/chapter_tree/binary_tree_bfs_test.go b/codes/go/chapter_tree/binary_tree_bfs_test.go index d32dbdca9..7b1bbf1f5 100644 --- a/codes/go/chapter_tree/binary_tree_bfs_test.go +++ b/codes/go/chapter_tree/binary_tree_bfs_test.go @@ -14,7 +14,7 @@ import ( func TestLevelOrder(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - root := ArrToTree([]int{1, 2, 3, 4, 5, 6, 7}) + root := ArrToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树: ") PrintTree(root) diff --git a/codes/go/chapter_tree/binary_tree_dfs_test.go b/codes/go/chapter_tree/binary_tree_dfs_test.go index b0db8086c..9e0dfe22f 100644 --- a/codes/go/chapter_tree/binary_tree_dfs_test.go +++ b/codes/go/chapter_tree/binary_tree_dfs_test.go @@ -14,7 +14,7 @@ import ( func TestPreInPostOrderTraversal(t *testing.T) { /* 初始化二叉树 */ // 这里借助了一个从数组直接生成二叉树的函数 - root := ArrToTree([]int{1, 2, 3, 4, 5, 6, 7}) + root := ArrToTree([]any{1, 2, 3, 4, 5, 6, 7}) fmt.Println("\n初始化二叉树: ") PrintTree(root) diff --git a/codes/go/pkg/tree_node.go b/codes/go/pkg/tree_node.go index b1e630e67..23eb3d154 100644 --- a/codes/go/pkg/tree_node.go +++ b/codes/go/pkg/tree_node.go @@ -23,11 +23,12 @@ func NewTreeNode(v int) *TreeNode { } // ArrToTree Generate a binary tree given an array -func ArrToTree(arr []int) *TreeNode { +func ArrToTree(arr []any) *TreeNode { if len(arr) <= 0 { return nil } - root := NewTreeNode(arr[0]) + // TreeNode only accept integer value for now. + root := NewTreeNode(arr[0].(int)) // Let container.list as queue queue := list.New() queue.PushBack(root) @@ -37,13 +38,17 @@ func ArrToTree(arr []int) *TreeNode { node := queue.Remove(queue.Front()).(*TreeNode) i++ if i < len(arr) { - node.Left = NewTreeNode(arr[i]) - queue.PushBack(node.Left) + if arr[i] != nil { + node.Left = NewTreeNode(arr[i].(int)) + queue.PushBack(node.Left) + } } i++ if i < len(arr) { - node.Right = NewTreeNode(arr[i]) - queue.PushBack(node.Right) + if arr[i] != nil { + node.Right = NewTreeNode(arr[i].(int)) + queue.PushBack(node.Right) + } } } return root diff --git a/codes/go/pkg/tree_node_test.go b/codes/go/pkg/tree_node_test.go index bb1885ee1..ec7831bf7 100644 --- a/codes/go/pkg/tree_node_test.go +++ b/codes/go/pkg/tree_node_test.go @@ -10,7 +10,7 @@ import ( ) func TestTreeNode(t *testing.T) { - arr := []int{2, 3, 5, 6, 7} + arr := []any{1, 2, 3, nil, 5, 6, nil} node := ArrToTree(arr) // print tree From 0243957015fb75675ae48ea1dcab8038aafb72e6 Mon Sep 17 00:00:00 2001 From: reanon <793584285@qq.com> Date: Sun, 8 Jan 2023 20:29:13 +0800 Subject: [PATCH 14/21] doc(code): modify go code in docs --- docs/chapter_array_and_linkedlist/list.md | 25 +++--- .../space_complexity.md | 16 ++-- docs/chapter_hashing/hash_map.md | 24 +++--- docs/chapter_sorting/merge_sort.md | 19 +++-- docs/chapter_sorting/quick_sort.md | 10 +-- docs/chapter_stack_and_queue/queue.md | 78 +++++++++++-------- docs/chapter_stack_and_queue/stack.md | 78 +++++++++++-------- docs/chapter_tree/binary_search_tree.md | 10 +-- 8 files changed, 143 insertions(+), 117 deletions(-) diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index d1109d65b..ca4774a04 100644 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -864,7 +864,7 @@ comments: true ```go title="my_list.go" /* 列表类简易实现 */ - type MyList struct { + type myList struct { numsCapacity int nums []int numsSize int @@ -872,8 +872,8 @@ comments: true } /* 构造函数 */ - func newMyList() *MyList { - return &MyList{ + func newMyList() *myList { + return &myList{ numsCapacity: 10, // 列表容量 nums: make([]int, 10), // 数组(存储列表元素) numsSize: 0, // 列表长度(即当前元素数量) @@ -882,17 +882,17 @@ comments: true } /* 获取列表长度(即当前元素数量) */ - func (l *MyList) size() int { + func (l *myList) size() int { return l.numsSize } /* 获取列表容量 */ - func (l *MyList) capacity() int { + func (l *myList) capacity() int { return l.numsCapacity } /* 访问元素 */ - func (l *MyList) get(index int) int { + func (l *myList) get(index int) int { // 索引如果越界则抛出异常,下同 if index >= l.numsSize { panic("索引越界") @@ -901,7 +901,7 @@ comments: true } /* 更新元素 */ - func (l *MyList) set(num, index int) { + func (l *myList) set(num, index int) { if index >= l.numsSize { panic("索引越界") } @@ -909,7 +909,7 @@ comments: true } /* 尾部添加元素 */ - func (l *MyList) add(num int) { + func (l *myList) add(num int) { // 元素数量超出容量时,触发扩容机制 if l.numsSize == l.numsCapacity { l.extendCapacity() @@ -920,7 +920,7 @@ comments: true } /* 中间插入元素 */ - func (l *MyList) insert(num, index int) { + func (l *myList) insert(num, index int) { if index >= l.numsSize { panic("索引越界") } @@ -938,20 +938,23 @@ comments: true } /* 删除元素 */ - func (l *MyList) Remove(index int) { + func (l *myList) remove(index int) int { if index >= l.numsSize { panic("索引越界") } + num := l.nums[index] // 索引 i 之后的元素都向前移动一位 for j := index; j < l.numsSize-1; j++ { l.nums[j] = l.nums[j+1] } // 更新元素数量 l.numsSize-- + // 返回被删除元素 + return num } /* 列表扩容 */ - func (l *MyList) extendCapacity() { + func (l *myList) extendCapacity() { // 新建一个长度为 self.__size 的数组,并将原数组拷贝到新数组 l.nums = append(l.nums, make([]int, l.numsCapacity*(l.extendRatio-1))...) // 更新列表容量 diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index 1756f2e24..f563f4953 100644 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -103,14 +103,14 @@ comments: true ```go title="" /* 结构体 */ - type Node struct { + type node struct { val int - next *Node + next *node } - - /* 创建 Node 结构体 */ - func newNode(val int) *Node { - return &Node{val: val} + + /* 创建 node 结构体 */ + func newNode(val int) *node { + return &node{val: val} } /* 函数 */ @@ -687,7 +687,7 @@ $$ // 长度为 n 的数组占用 O(n) 空间 _ = make([]int, n) // 长度为 n 的列表占用 O(n) 空间 - var nodes []*Node + var nodes []*node for i := 0; i < n; i++ { nodes = append(nodes, newNode(i)) } @@ -1108,7 +1108,7 @@ $$ ```go title="space_complexity.go" /* 指数阶(建立满二叉树) */ - func buildTree(n int) *TreeNode { + func buildTree(n int) *treeNode { if n == 0 { return nil } diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index 3a69d88fc..2d8a66a5d 100644 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -524,30 +524,30 @@ $$ ```go title="array_hash_map.go" /* 键值对 int->String */ - type Entry struct { + type entry struct { key int val string } /* 基于数组简易实现的哈希表 */ - type ArrayHashMap struct { - bucket []*Entry + type arrayHashMap struct { + bucket []*entry } - func newArrayHashMap() *ArrayHashMap { + func newArrayHashMap() *arrayHashMap { // 初始化一个长度为 100 的桶(数组) - bucket := make([]*Entry, 100) - return &ArrayHashMap{bucket: bucket} + bucket := make([]*entry, 100) + return &arrayHashMap{bucket: bucket} } /* 哈希函数 */ - func (a *ArrayHashMap) hashFunc(key int) int { + func (a *arrayHashMap) hashFunc(key int) int { index := key % 100 return index } /* 查询操作 */ - func (a *ArrayHashMap) get(key int) string { + func (a *arrayHashMap) get(key int) string { index := a.hashFunc(key) pair := a.bucket[index] if pair == nil { @@ -557,16 +557,16 @@ $$ } /* 添加操作 */ - func (a *ArrayHashMap) put(key int, val string) { - pair := &Entry{key: key, val: val} + func (a *arrayHashMap) put(key int, val string) { + pair := &entry{key: key, val: val} index := a.hashFunc(key) a.bucket[index] = pair } /* 删除操作 */ - func (a *ArrayHashMap) remove(key int) { + func (a *arrayHashMap) remove(key int) { index := a.hashFunc(key) - // 置为空字符,代表删除 + // 置为 nil ,代表删除 a.bucket[index] = nil } ``` diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index 396baa089..ae0cfc7c2 100644 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -201,36 +201,35 @@ comments: true 右子数组区间 [mid + 1, right] */ func merge(nums []int, left, mid, right int) { - // 初始化辅助数组 借助 copy模块 + // 初始化辅助数组 借助 copy 模块 tmp := make([]int, right-left+1) for i := left; i <= right; i++ { tmp[i-left] = nums[i] } // 左子数组的起始索引和结束索引 - left_start, left_end := left-left, mid-left + leftStart, leftEnd := left-left, mid-left // 右子数组的起始索引和结束索引 - right_start, right_end := mid+1-left, right-left + rightStart, rightEnd := mid+1-left, right-left // i, j 分别指向左子数组、右子数组的首元素 - i, j := left_start, right_start + i, j := leftStart, rightStart // 通过覆盖原数组 nums 来合并左子数组和右子数组 for k := left; k <= right; k++ { // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ - if i > left_end { + if i > leftEnd { nums[k] = tmp[j] j++ - // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ - } else if j > right_end || tmp[i] <= tmp[j] { + // 否则,若“右子数组已全部合并完”或“左子数组元素 <= 右子数组元素”,则选取左子数组元素,并且 i++ + } else if j > rightEnd || tmp[i] <= tmp[j] { nums[k] = tmp[i] i++ - // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + // 否则,若“左右子数组都未全部合并完”且“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ } else { nums[k] = tmp[j] j++ } } } - - /* 归并排序 */ + func mergeSort(nums []int, left, right int) { // 终止条件 if left >= right { diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index f8dcc3238..3eeac9c65 100644 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -111,21 +111,21 @@ comments: true ```go title="quick_sort.go" /* 哨兵划分 */ func partition(nums []int, left, right int) int { - //以 nums[left] 作为基准数 + // 以 nums[left] 作为基准数 i, j := left, right for i < j { for i < j && nums[j] >= nums[left] { - j-- //从右向左找首个小于基准数的元素 + j-- // 从右向左找首个小于基准数的元素 } for i < j && nums[i] <= nums[left] { - i++ //从左向右找首个大于基准数的元素 + i++ // 从左向右找首个大于基准数的元素 } //元素交换 nums[i], nums[j] = nums[j], nums[i] } - //将基准数交换至两子数组的分界线 + // 将基准数交换至两子数组的分界线 nums[i], nums[left] = nums[left], nums[i] - return i //返回基准数的索引 + return i // 返回基准数的索引 } ``` diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index ce47edb38..0c589fa69 100644 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -404,43 +404,49 @@ comments: true ```go title="linkedlist_queue.go" /* 基于链表实现的队列 */ - type LinkedListQueue struct { + type linkedListQueue struct { // 使用内置包 list 来实现队列 data *list.List } - // NewLinkedListQueue 初始化链表 - func NewLinkedListQueue() *LinkedListQueue { - return &LinkedListQueue{ + + // newLinkedListQueue 初始化链表 + func newLinkedListQueue() *linkedListQueue { + return &linkedListQueue{ data: list.New(), } } - // Offer 入队 - func (s *LinkedListQueue) Offer(value any) { + + // offer 入队 + func (s *linkedListQueue) offer(value any) { s.data.PushBack(value) } - // Poll 出队 - func (s *LinkedListQueue) Poll() any { - if s.IsEmpty() { + + // poll 出队 + func (s *linkedListQueue) poll() any { + if s.isEmpty() { return nil } e := s.data.Front() s.data.Remove(e) return e.Value } - // Peek 访问队首元素 - func (s *LinkedListQueue) Peek() any { - if s.IsEmpty() { + + // peek 访问队首元素 + func (s *linkedListQueue) peek() any { + if s.isEmpty() { return nil } e := s.data.Front() return e.Value } - // Size 获取队列的长度 - func (s *LinkedListQueue) Size() int { + + // size 获取队列的长度 + func (s *linkedListQueue) size() int { return s.data.Len() } - // IsEmpty 判断队列是否为空 - func (s *LinkedListQueue) IsEmpty() bool { + + // isEmpty 判断队列是否为空 + func (s *linkedListQueue) isEmpty() bool { return s.data.Len() == 0 } ``` @@ -805,34 +811,38 @@ comments: true ```go title="array_queue.go" /* 基于环形数组实现的队列 */ - type ArrayQueue struct { + type arrayQueue struct { data []int // 用于存储队列元素的数组 capacity int // 队列容量(即最多容量的元素个数) front int // 头指针,指向队首 rear int // 尾指针,指向队尾 + 1 } - // NewArrayQueue 基于环形数组实现的队列 - func NewArrayQueue(capacity int) *ArrayQueue { - return &ArrayQueue{ + + // newArrayQueue 基于环形数组实现的队列 + func newArrayQueue(capacity int) *arrayQueue { + return &arrayQueue{ data: make([]int, capacity), capacity: capacity, front: 0, rear: 0, } } - // Size 获取队列的长度 - func (q *ArrayQueue) Size() int { + + // size 获取队列的长度 + func (q *arrayQueue) size() int { size := (q.capacity + q.rear - q.front) % q.capacity return size } - // IsEmpty 判断队列是否为空 - func (q *ArrayQueue) IsEmpty() bool { + + // isEmpty 判断队列是否为空 + func (q *arrayQueue) isEmpty() bool { return q.rear-q.front == 0 } - // Offer 入队 - func (q *ArrayQueue) Offer(v int) { + + // offer 入队 + func (q *arrayQueue) offer(v int) { // 当 rear == capacity 表示队列已满 - if q.Size() == q.capacity { + if q.size() == q.capacity { return } // 尾结点后添加 @@ -840,9 +850,10 @@ comments: true // 尾指针向后移动一位,越过尾部后返回到数组头部 q.rear = (q.rear + 1) % q.capacity } - // Poll 出队 - func (q *ArrayQueue) Poll() any { - if q.IsEmpty() { + + // poll 出队 + func (q *arrayQueue) poll() any { + if q.isEmpty() { return nil } v := q.data[q.front] @@ -850,9 +861,10 @@ comments: true q.front = (q.front + 1) % q.capacity return v } - // Peek 访问队首元素 - func (q *ArrayQueue) Peek() any { - if q.IsEmpty() { + + // peek 访问队首元素 + func (q *arrayQueue) peek() any { + if q.isEmpty() { return nil } v := q.data[q.front] diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index 374de7424..8bb343c4f 100644 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -378,43 +378,49 @@ comments: true ```go title="linkedlist_stack.go" /* 基于链表实现的栈 */ - type LinkedListStack struct { + type linkedListStack struct { // 使用内置包 list 来实现栈 data *list.List } - // NewLinkedListStack 初始化链表 - func NewLinkedListStack() *LinkedListStack { - return &LinkedListStack{ + + // newLinkedListStack 初始化链表 + func newLinkedListStack() *linkedListStack { + return &linkedListStack{ data: list.New(), } } - // Push 入栈 - func (s *LinkedListStack) Push(value int) { + + // push 入栈 + func (s *linkedListStack) push(value int) { s.data.PushBack(value) } - // Pop 出栈 - func (s *LinkedListStack) Pop() any { - if s.IsEmpty() { + + // pop 出栈 + func (s *linkedListStack) pop() any { + if s.isEmpty() { return nil } e := s.data.Back() s.data.Remove(e) return e.Value } - // Peek 访问栈顶元素 - func (s *LinkedListStack) Peek() any { - if s.IsEmpty() { + + // peek 访问栈顶元素 + func (s *linkedListStack) peek() any { + if s.isEmpty() { return nil } e := s.data.Back() return e.Value } - // Size 获取栈的长度 - func (s *LinkedListStack) Size() int { + + // size 获取栈的长度 + func (s *linkedListStack) size() int { return s.data.Len() } - // IsEmpty 判断栈是否为空 - func (s *LinkedListStack) IsEmpty() bool { + + // isEmpty 判断栈是否为空 + func (s *linkedListStack) isEmpty() bool { return s.data.Len() == 0 } ``` @@ -716,41 +722,47 @@ comments: true ```go title="array_stack.go" /* 基于数组实现的栈 */ - type ArrayStack struct { + type arrayStack struct { data []int // 数据 } - func NewArrayStack() *ArrayStack { - return &ArrayStack{ + + func newArrayStack() *arrayStack { + return &arrayStack{ // 设置栈的长度为 0,容量为 16 data: make([]int, 0, 16), } } - // Size 栈的长度 - func (s *ArrayStack) Size() int { + + // size 栈的长度 + func (s *arrayStack) size() int { return len(s.data) } - // IsEmpty 栈是否为空 - func (s *ArrayStack) IsEmpty() bool { - return s.Size() == 0 + + // isEmpty 栈是否为空 + func (s *arrayStack) isEmpty() bool { + return s.size() == 0 } - // Push 入栈 - func (s *ArrayStack) Push(v int) { + + // push 入栈 + func (s *arrayStack) push(v int) { // 切片会自动扩容 s.data = append(s.data, v) } - // Pop 出栈 - func (s *ArrayStack) Pop() any { + + // pop 出栈 + func (s *arrayStack) pop() any { // 弹出栈前,先判断是否为空 - if s.IsEmpty() { + if s.isEmpty() { return nil } - val := s.Peek() + val := s.peek() s.data = s.data[:len(s.data)-1] return val } - // Peek 获取栈顶元素 - func (s *ArrayStack) Peek() any { - if s.IsEmpty() { + + // peek 获取栈顶元素 + func (s *arrayStack) peek() any { + if s.isEmpty() { return nil } val := s.data[len(s.data)-1] diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index 979612852..e3ddfaaac 100644 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -103,7 +103,7 @@ comments: true ```go title="binary_search_tree.go" /* 查找结点 */ - func (bst *BinarySearchTree) Search(num int) *TreeNode { + func (bst *binarySearchTree) search(num int) *TreeNode { node := bst.root // 循环查找,越过叶结点后跳出 for node != nil { @@ -299,7 +299,7 @@ comments: true ```go title="binary_search_tree.go" /* 插入结点 */ - func (bst *BinarySearchTree) Insert(num int) *TreeNode { + func (bst *binarySearchTree) insert(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { @@ -609,7 +609,7 @@ comments: true ```go title="binary_search_tree.go" /* 删除结点 */ - func (bst *BinarySearchTree) Remove(num int) *TreeNode { + func (bst *binarySearchTree) remove(num int) *TreeNode { cur := bst.root // 若树为空,直接提前返回 if cur == nil { @@ -653,10 +653,10 @@ comments: true // 子结点数为 2 } else { // 获取中序遍历中待删除结点 cur 的下一个结点 - next := bst.GetInOrderNext(cur) + next := bst.getInOrderNext(cur) temp := next.Val // 递归删除结点 next - bst.Remove(next.Val) + bst.remove(next.Val) // 将 next 的值复制给 cur cur.Val = temp } From b6abf2b09280d746f261e5508356e369acc189c0 Mon Sep 17 00:00:00 2001 From: sjinzh <99076655+sjinzh@users.noreply.github.com> Date: Sun, 8 Jan 2023 20:38:48 +0800 Subject: [PATCH 15/21] (PR #217)update a .gitignore file in the codes/zig dir --- codes/zig/.gitignore | 2 + codes/zig/build.zig | 46 ++++++++++++++ .../leetcode_two_sum.zig | 61 +++++++++++++++++++ .../worst_best_time_complexity.zig | 9 ++- codes/zig/include/PrintUtil.zig | 13 ++++ codes/zig/include/include.zig | 5 ++ 6 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 codes/zig/.gitignore create mode 100644 codes/zig/build.zig create mode 100644 codes/zig/chapter_computational_complexity/leetcode_two_sum.zig create mode 100644 codes/zig/include/PrintUtil.zig create mode 100644 codes/zig/include/include.zig diff --git a/codes/zig/.gitignore b/codes/zig/.gitignore new file mode 100644 index 000000000..4a0641ed7 --- /dev/null +++ b/codes/zig/.gitignore @@ -0,0 +1,2 @@ +zig-cache/ +zig-out/ \ No newline at end of file diff --git a/codes/zig/build.zig b/codes/zig/build.zig new file mode 100644 index 000000000..b04b3fd18 --- /dev/null +++ b/codes/zig/build.zig @@ -0,0 +1,46 @@ +const std = @import("std"); + +// zig version 0.10.0 +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + const mode = b.standardReleaseOptions(); + + // "chapter_computational_complexity/time_complexity.zig" + // Run Command: zig build run_time_complexity + const exe_time_complexity = b.addExecutable("time_complexity", "chapter_computational_complexity/time_complexity.zig"); + exe_time_complexity.addPackagePath("include", "include/include.zig"); + exe_time_complexity.setTarget(target); + exe_time_complexity.setBuildMode(mode); + exe_time_complexity.install(); + const run_cmd_time_complexity = exe_time_complexity.run(); + run_cmd_time_complexity.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd_time_complexity.addArgs(args); + const run_step_time_complexity = b.step("run_time_complexity", "Run time_complexity"); + run_step_time_complexity.dependOn(&run_cmd_time_complexity.step); + + // "chapter_computational_complexity/worst_best_time_complexity.zig" + // Run Command: zig build run_worst_best_time_complexity + const exe_worst_best_time_complexity = b.addExecutable("worst_best_time_complexity", "chapter_computational_complexity/worst_best_time_complexity.zig"); + exe_worst_best_time_complexity.addPackagePath("include", "include/include.zig"); + exe_worst_best_time_complexity.setTarget(target); + exe_worst_best_time_complexity.setBuildMode(mode); + exe_worst_best_time_complexity.install(); + const run_cmd_worst_best_time_complexity = exe_worst_best_time_complexity.run(); + run_cmd_worst_best_time_complexity.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd_worst_best_time_complexity.addArgs(args); + const run_step_worst_best_time_complexity = b.step("run_worst_best_time_complexity", "Run worst_best_time_complexity"); + run_step_worst_best_time_complexity.dependOn(&run_cmd_worst_best_time_complexity.step); + + // "chapter_computational_complexity/leetcode_two_sum.zig" + // Run Command: zig build run_leetcode_two_sum + const exe_leetcode_two_sum = b.addExecutable("leetcode_two_sum", "chapter_computational_complexity/leetcode_two_sum.zig"); + exe_leetcode_two_sum.addPackagePath("include", "include/include.zig"); + exe_leetcode_two_sum.setTarget(target); + exe_leetcode_two_sum.setBuildMode(mode); + exe_leetcode_two_sum.install(); + const run_cmd_leetcode_two_sum = exe_leetcode_two_sum.run(); + run_cmd_leetcode_two_sum.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd_leetcode_two_sum.addArgs(args); + const run_step_leetcode_two_sum = b.step("run_leetcode_two_sum", "Run leetcode_two_sum"); + run_step_leetcode_two_sum.dependOn(&run_cmd_leetcode_two_sum.step); +} diff --git a/codes/zig/chapter_computational_complexity/leetcode_two_sum.zig b/codes/zig/chapter_computational_complexity/leetcode_two_sum.zig new file mode 100644 index 000000000..66b96f95e --- /dev/null +++ b/codes/zig/chapter_computational_complexity/leetcode_two_sum.zig @@ -0,0 +1,61 @@ +// File: leetcode_two_sum.zig +// Created Time: 2023-01-07 +// Author: sjinzh (sjinzh@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +const SolutionBruteForce = struct { + pub fn twoSum(self: *SolutionBruteForce, nums: []i32, target: i32) [2]i32 { + _ = self; + var size: usize = nums.len; + var i: usize = 0; + // 两层循环,时间复杂度 O(n^2) + while (i < size - 1) : (i += 1) { + var j = i + 1; + while (j < size) : (j += 1) { + if (nums[i] + nums[j] == target) { + return [_]i32{@intCast(i32, i), @intCast(i32, j)}; + } + } + } + return undefined; + } +}; + +const SolutionHashMap = struct { + pub fn twoSum(self: *SolutionHashMap, nums: []i32, target: i32) ![2]i32 { + _ = self; + var size: usize = nums.len; + // 辅助哈希表,空间复杂度 O(n) + var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer dic.deinit(); + var i: usize = 0; + // 单层循环,时间复杂度 O(n) + while (i < size) : (i += 1) { + if (dic.contains(target - nums[i])) { + return [_]i32{dic.get(target - nums[i]).?, @intCast(i32, i)}; + } + try dic.put(nums[i], @intCast(i32, i)); + } + return undefined; + } +}; + +// Driver Code +pub fn main() !void { + // ======= Test Case ======= + var nums = [_]i32{ 2, 7, 11, 15 }; + var target: i32 = 9; + // 方法一 + var slt1 = SolutionBruteForce{}; + var res = slt1.twoSum(&nums, target); + std.debug.print("方法一 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + // 方法二 + var slt2 = SolutionHashMap{}; + res = try slt2.twoSum(&nums, target); + std.debug.print("方法二 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); +} + diff --git a/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig b/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig index dacf45952..bd2a1464a 100644 --- a/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig +++ b/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig @@ -3,6 +3,7 @@ // Author: sjinzh (sjinzh@gmail.com) const std = @import("std"); +const inc = @import("include"); // 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 pub fn randomNumbers(comptime n: usize) [n]i32 { @@ -26,17 +27,15 @@ pub fn findOne(nums: []i32) i32 { } // Driver Code -pub fn main() !void { +pub fn main() void { var i: i32 = 0; while (i < 10) : (i += 1) { const n: usize = 100; var nums = randomNumbers(n); var index = findOne(&nums); std.debug.print("\n数组 [ 1, 2, ..., n ] 被打乱后 = ", .{}); - for (nums) |num, j| { - std.debug.print("{}{s}", .{num, if (j == nums.len-1) "" else "," }); - } - std.debug.print("\n数字 1 的索引为 {}\n", .{index}); + inc.PrintUtil.printArray(i32, &nums); + std.debug.print("数字 1 的索引为 {}\n", .{index}); } } diff --git a/codes/zig/include/PrintUtil.zig b/codes/zig/include/PrintUtil.zig new file mode 100644 index 000000000..5adac2a6e --- /dev/null +++ b/codes/zig/include/PrintUtil.zig @@ -0,0 +1,13 @@ +// File: TreeNode.zig +// Created Time: 2023-01-07 +// Author: sjinzh (sjinzh@gmail.com) + +const std = @import("std"); + +// Print an Array +pub fn printArray(comptime T: type, nums: []T) void { + std.debug.print("[", .{}); + for (nums) |num, j| { + std.debug.print("{}{s}", .{num, if (j == nums.len-1) "]\n" else ", " }); + } +} diff --git a/codes/zig/include/include.zig b/codes/zig/include/include.zig new file mode 100644 index 000000000..b4f347821 --- /dev/null +++ b/codes/zig/include/include.zig @@ -0,0 +1,5 @@ +// File: include.zig +// Created Time: 2023-01-04 +// Author: sjinzh (sjinzh@gmail.com) + +pub const PrintUtil = @import("PrintUtil.zig"); \ No newline at end of file From 3b52df2a8f2725f88db56c4696ea9a3ef48b1663 Mon Sep 17 00:00:00 2001 From: nuomi1 Date: Sun, 8 Jan 2023 20:53:24 +0800 Subject: [PATCH 16/21] style: update comment format --- .../space_complexity.swift | 16 +++++------ .../time_complexity.swift | 23 +++++++-------- .../worst_best_time_complexity.swift | 6 ++-- docs/chapter_array_and_linkedlist/array.md | 14 +++++----- .../space_complexity.md | 20 ++++++------- .../time_complexity.md | 28 +++++++++---------- .../chapter_data_structure/data_and_memory.md | 2 +- 7 files changed, 55 insertions(+), 54 deletions(-) diff --git a/codes/swift/chapter_computational_complexity/space_complexity.swift b/codes/swift/chapter_computational_complexity/space_complexity.swift index 92a1187a0..fdd29f8e2 100644 --- a/codes/swift/chapter_computational_complexity/space_complexity.swift +++ b/codes/swift/chapter_computational_complexity/space_complexity.swift @@ -6,14 +6,14 @@ import utils -// 函数 +/* 函数 */ @discardableResult func function() -> Int { // do something return 0 } -// 常数阶 +/* 常数阶 */ func constant(n: Int) { // 常量、变量、对象占用 O(1) 空间 let a = 0 @@ -30,7 +30,7 @@ func constant(n: Int) { } } -// 线性阶 +/* 线性阶 */ func linear(n: Int) { // 长度为 n 的数组占用 O(n) 空间 let nums = Array(repeating: 0, count: n) @@ -40,7 +40,7 @@ func linear(n: Int) { let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) } -// 线性阶(递归实现) +/* 线性阶(递归实现) */ func linearRecur(n: Int) { print("递归 n = \(n)") if n == 1 { @@ -49,13 +49,13 @@ func linearRecur(n: Int) { linearRecur(n: n - 1) } -// 平方阶 +/* 平方阶 */ func quadratic(n: Int) { // 二维列表占用 O(n^2) 空间 let numList = Array(repeating: Array(repeating: 0, count: n), count: n) } -// 平方阶(递归实现) +/* 平方阶(递归实现) */ @discardableResult func quadraticRecur(n: Int) -> Int { if n <= 0 { @@ -67,7 +67,7 @@ func quadraticRecur(n: Int) -> Int { return quadraticRecur(n: n - 1) } -// 指数阶(建立满二叉树) +/* 指数阶(建立满二叉树) */ func buildTree(n: Int) -> TreeNode? { if n == 0 { return nil @@ -80,7 +80,7 @@ func buildTree(n: Int) -> TreeNode? { @main enum SpaceComplexity { - // Driver Code + /* Driver Code */ static func main() { let n = 5 // 常数阶 diff --git a/codes/swift/chapter_computational_complexity/time_complexity.swift b/codes/swift/chapter_computational_complexity/time_complexity.swift index 44addb800..a0e0a9cff 100644 --- a/codes/swift/chapter_computational_complexity/time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -4,7 +4,7 @@ * Author: nuomi1 (nuomi1@qq.com) */ -// 常数阶 +/* 常数阶 */ func constant(n: Int) -> Int { var count = 0 let size = 100_000 @@ -14,7 +14,7 @@ func constant(n: Int) -> Int { return count } -// 线性阶 +/* 线性阶 */ func linear(n: Int) -> Int { var count = 0 for _ in 0 ..< n { @@ -23,7 +23,7 @@ func linear(n: Int) -> Int { return count } -// 线性阶(遍历数组) +/* 线性阶(遍历数组) */ func arrayTraversal(nums: [Int]) -> Int { var count = 0 // 循环次数与数组长度成正比 @@ -33,7 +33,7 @@ func arrayTraversal(nums: [Int]) -> Int { return count } -// 平方阶 +/* 平方阶 */ func quadratic(n: Int) -> Int { var count = 0 // 循环次数与数组长度成平方关系 @@ -45,7 +45,7 @@ func quadratic(n: Int) -> Int { return count } -// 平方阶(冒泡排序) +/* 平方阶(冒泡排序) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // 计数器 // 外循环:待排序元素数量为 n-1, n-2, ..., 1 @@ -64,7 +64,7 @@ func bubbleSort(nums: inout [Int]) -> Int { return count } -// 指数阶(循环实现) +/* 指数阶(循环实现) */ func exponential(n: Int) -> Int { var count = 0 var base = 1 @@ -79,7 +79,7 @@ func exponential(n: Int) -> Int { return count } -// 指数阶(递归实现) +/* 指数阶(递归实现) */ func expRecur(n: Int) -> Int { if n == 1 { return 1 @@ -87,7 +87,7 @@ func expRecur(n: Int) -> Int { return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 } -// 对数阶(循环实现) +/* 对数阶(循环实现) */ func logarithmic(n: Int) -> Int { var count = 0 var n = n @@ -98,7 +98,7 @@ func logarithmic(n: Int) -> Int { return count } -// 对数阶(递归实现) +/* 对数阶(递归实现) */ func logRecur(n: Int) -> Int { if n <= 1 { return 0 @@ -106,7 +106,7 @@ func logRecur(n: Int) -> Int { return logRecur(n: n / 2) + 1 } -// 线性对数阶 +/* 线性对数阶 */ func linearLogRecur(n: Double) -> Int { if n <= 1 { return 1 @@ -118,7 +118,7 @@ func linearLogRecur(n: Double) -> Int { return count } -// 阶乘阶(递归实现) +/* 阶乘阶(递归实现) */ func factorialRecur(n: Int) -> Int { if n == 0 { return 1 @@ -133,6 +133,7 @@ func factorialRecur(n: Int) -> Int { @main enum TimeComplexity { + /* Driver Code */ static func main() { // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 let n = 8 diff --git a/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift b/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift index 09d7de1f4..34f0518f1 100644 --- a/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift @@ -4,7 +4,7 @@ * Author: nuomi1 (nuomi1@qq.com) */ -// 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 +/* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ func randomNumbers(n: Int) -> [Int] { // 生成数组 nums = { 1, 2, 3, ..., n } var nums = Array(1 ... n) @@ -13,7 +13,7 @@ func randomNumbers(n: Int) -> [Int] { return nums } -// 查找数组 nums 中数字 1 所在索引 +/* 查找数组 nums 中数字 1 所在索引 */ func findOne(nums: [Int]) -> Int { for i in nums.indices { if nums[i] == 1 { @@ -25,7 +25,7 @@ func findOne(nums: [Int]) -> Int { @main enum WorstBestTimeComplexity { - // Driver Code + /* Driver Code */ static func main() { for _ in 0 ..< 10 { let n = 100 diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 461cdcf37..2cf096137 100644 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -84,7 +84,7 @@ comments: true === "Swift" ```swift title="array.swift" - // 初始化数组 + /* 初始化数组 */ let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] let nums = [1, 3, 2, 5, 4] ``` @@ -204,7 +204,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Swift" ```swift title="array.swift" - // 随机返回一个数组元素 + /* 随机返回一个数组元素 */ func randomAccess(nums: [Int]) -> Int { // 在区间 [0, nums.count) 中随机抽取一个数字 let randomIndex = nums.indices.randomElement()! @@ -341,7 +341,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Swift" ```swift title="array.swift" - // 扩展数组长度 + /* 扩展数组长度 */ func extend(nums: [Int], enlarge: Int) -> [Int] { // 初始化一个扩展长度后的数组 var res = Array(repeating: 0, count: nums.count + enlarge) @@ -526,7 +526,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Swift" ```swift title="array.swift" - // 在数组的索引 index 处插入元素 num + /* 在数组的索引 index 处插入元素 num */ func insert(nums: inout [Int], num: Int, index: Int) { // 把索引 index 以及之后的所有元素向后移动一位 for i in sequence(first: nums.count - 1, next: { $0 > index + 1 ? $0 - 1 : nil }) { @@ -536,7 +536,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex nums[index] = num } - // 删除索引 index 处元素 + /* 删除索引 index 处元素 */ func remove(nums: inout [Int], index: Int) { let count = nums.count // 把索引 index 之后的所有元素向前移动一位 @@ -674,7 +674,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Swift" ```swift title="array.swift" - // 遍历数组 + /* 遍历数组 */ func traverse(nums: [Int]) { var count = 0 // 通过索引遍历数组 @@ -793,7 +793,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex === "Swift" ```swift title="array.swift" - // 在数组中查找指定元素 + /* 在数组中查找指定元素 */ func find(nums: [Int], target: Int) -> Int { for i in nums.indices { if nums[i] == target { diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index 1756f2e24..939b61ecf 100644 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -177,7 +177,7 @@ comments: true === "Swift" ```swift title="" - // 类 + /* 类 */ class Node { var val: Int var next: Node? @@ -187,7 +187,7 @@ comments: true } } - // 函数 + /* 函数 */ func function() -> Int { // do something... return 0 @@ -436,14 +436,14 @@ comments: true return 0 } - // 循环 O(1) + /* 循环 O(1) */ func loop(n: Int) { for _ in 0 ..< n { function() } } - // 递归 O(n) + /* 递归 O(n) */ func recur(n: Int) { if n == 1 { return @@ -604,7 +604,7 @@ $$ === "Swift" ```swift title="space_complexity.swift" - // 常数阶 + /* 常数阶 */ func constant(n: Int) { // 常量、变量、对象占用 O(1) 空间 let a = 0 @@ -743,7 +743,7 @@ $$ === "Swift" ```swift title="space_complexity.swift" - // 线性阶 + /* 线性阶 */ func linear(n: Int) { // 长度为 n 的数组占用 O(n) 空间 let nums = Array(repeating: 0, count: n) @@ -834,7 +834,7 @@ $$ === "Swift" ```swift title="space_complexity.swift" - // 线性阶(递归实现) + /* 线性阶(递归实现) */ func linearRecur(n: Int) { print("递归 n = \(n)") if n == 1 { @@ -954,7 +954,7 @@ $$ === "Swift" ```swift title="space_complexity.swift" - // 平方阶 + /* 平方阶 */ func quadratic(n: Int) { // 二维列表占用 O(n^2) 空间 let numList = Array(repeating: Array(repeating: 0, count: n), count: n) @@ -1047,7 +1047,7 @@ $$ === "Swift" ```swift title="space_complexity.swift" - // 平方阶(递归实现) + /* 平方阶(递归实现) */ func quadraticRecur(n: Int) -> Int { if n <= 0 { return 0 @@ -1154,7 +1154,7 @@ $$ === "Swift" ```swift title="space_complexity.swift" - // 指数阶(建立满二叉树) + /* 指数阶(建立满二叉树) */ func buildTree(n: Int) -> TreeNode? { if n == 0 { return nil diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 7b0e66fbd..f6b6e9356 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -876,7 +876,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 常数阶 + /* 常数阶 */ func constant(n: Int) -> Int { var count = 0 let size = 100000 @@ -990,7 +990,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 线性阶 + /* 线性阶 */ func linear(n: Int) -> Int { var count = 0 for _ in 0 ..< n { @@ -1121,7 +1121,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 线性阶(遍历数组) + /* 线性阶(遍历数组) */ func arrayTraversal(nums: [Int]) -> Int { var count = 0 // 循环次数与数组长度成正比 @@ -1267,7 +1267,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 平方阶 + /* 平方阶 */ func quadratic(n: Int) -> Int { var count = 0 // 循环次数与数组长度成平方关系 @@ -1477,7 +1477,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 平方阶(冒泡排序) + /* 平方阶(冒泡排序) */ func bubbleSort(nums: inout [Int]) -> Int { var count = 0 // 计数器 // 外循环:待排序元素数量为 n-1, n-2, ..., 1 @@ -1656,7 +1656,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 指数阶(循环实现) + /* 指数阶(循环实现) */ func exponential(n: Int) -> Int { var count = 0 var base = 1 @@ -1764,7 +1764,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 指数阶(递归实现) + /* 指数阶(递归实现) */ func expRecur(n: Int) -> Int { if n == 1 { return 1 @@ -1896,7 +1896,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 对数阶(循环实现) + /* 对数阶(循环实现) */ func logarithmic(n: Int) -> Int { var count = 0 var n = n @@ -1999,7 +1999,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 对数阶(递归实现) + /* 对数阶(递归实现) */ func logRecur(n: Int) -> Int { if n <= 1 { return 0 @@ -2137,7 +2137,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 线性对数阶 + /* 线性对数阶 */ func linearLogRecur(n: Double) -> Int { if n <= 1 { return 1 @@ -2288,7 +2288,7 @@ $$ === "Swift" ```swift title="time_complexity.swift" - // 阶乘阶(递归实现) + /* 阶乘阶(递归实现) */ func factorialRecur(n: Int) -> Int { if n == 0 { return 1 @@ -2658,7 +2658,7 @@ $$ === "Swift" ```swift title="worst_best_time_complexity.swift" - // 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 + /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ func randomNumbers(n: Int) -> [Int] { // 生成数组 nums = { 1, 2, 3, ..., n } var nums = Array(1 ... n) @@ -2667,7 +2667,7 @@ $$ return nums } - // 查找数组 nums 中数字 1 所在索引 + /* 查找数组 nums 中数字 1 所在索引 */ func findOne(nums: [Int]) -> Int { for i in nums.indices { if nums[i] == 1 { @@ -2677,7 +2677,7 @@ $$ return -1 } - // Driver Code + /* Driver Code */ func main() { for _ in 0 ..< 10 { let n = 100 diff --git a/docs/chapter_data_structure/data_and_memory.md b/docs/chapter_data_structure/data_and_memory.md index 3a11fdf36..59cb74e45 100644 --- a/docs/chapter_data_structure/data_and_memory.md +++ b/docs/chapter_data_structure/data_and_memory.md @@ -117,7 +117,7 @@ comments: true === "Swift" ```swift title="" - // 使用多种「基本数据类型」来初始化「数组」 + /* 使用多种「基本数据类型」来初始化「数组」 */ let numbers = Array(repeating: Int(), count: 5) let decimals = Array(repeating: Double(), count: 5) let characters = Array(repeating: Character("a"), count: 5) From f3e9c2cf896fbfe5ab794a6b53e2351457f9f5bd Mon Sep 17 00:00:00 2001 From: nuomi1 Date: Sun, 8 Jan 2023 20:54:40 +0800 Subject: [PATCH 17/21] style: use string interpolation in print --- .../time_complexity.swift | 24 +++++++++---------- .../worst_best_time_complexity.swift | 4 ++-- .../time_complexity.md | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/codes/swift/chapter_computational_complexity/time_complexity.swift b/codes/swift/chapter_computational_complexity/time_complexity.swift index a0e0a9cff..41695cfd6 100644 --- a/codes/swift/chapter_computational_complexity/time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -137,36 +137,36 @@ enum TimeComplexity { static func main() { // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 let n = 8 - print("输入数据大小 n =", n) + print("输入数据大小 n = \(n)") var count = constant(n: n) - print("常数阶的计算操作数量 =", count) + print("常数阶的计算操作数量 = \(count)") count = linear(n: n) - print("线性阶的计算操作数量 =", count) + print("线性阶的计算操作数量 = \(count)") count = arrayTraversal(nums: Array(repeating: 0, count: n)) - print("线性阶(遍历数组)的计算操作数量 =", count) + print("线性阶(遍历数组)的计算操作数量 = \(count)") count = quadratic(n: n) - print("平方阶的计算操作数量 =", count) + print("平方阶的计算操作数量 = \(count)") var nums = Array(sequence(first: n, next: { $0 > 0 ? $0 - 1 : nil })) // [n,n-1,...,2,1] count = bubbleSort(nums: &nums) - print("平方阶(冒泡排序)的计算操作数量 =", count) + print("平方阶(冒泡排序)的计算操作数量 = \(count)") count = exponential(n: n) - print("指数阶(循环实现)的计算操作数量 =", count) + print("指数阶(循环实现)的计算操作数量 = \(count)") count = expRecur(n: n) - print("指数阶(递归实现)的计算操作数量 =", count) + print("指数阶(递归实现)的计算操作数量 = \(count)") count = logarithmic(n: n) - print("对数阶(循环实现)的计算操作数量 =", count) + print("对数阶(循环实现)的计算操作数量 = \(count)") count = logRecur(n: n) - print("对数阶(递归实现)的计算操作数量 =", count) + print("对数阶(递归实现)的计算操作数量 = \(count)") count = linearLogRecur(n: Double(n)) - print("线性对数阶(递归实现)的计算操作数量 =", count) + print("线性对数阶(递归实现)的计算操作数量 = \(count)") count = factorialRecur(n: n) - print("阶乘阶(递归实现)的计算操作数量 =", count) + print("阶乘阶(递归实现)的计算操作数量 = \(count)") } } diff --git a/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift b/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift index 34f0518f1..3dac661d2 100644 --- a/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift +++ b/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift @@ -31,8 +31,8 @@ enum WorstBestTimeComplexity { let n = 100 let nums = randomNumbers(n: n) let index = findOne(nums: nums) - print("数组 [ 1, 2, ..., n ] 被打乱后 =", nums) - print("数字 1 的索引为", index) + print("数组 [ 1, 2, ..., n ] 被打乱后 = \(nums)") + print("数字 1 的索引为 \(index)") } } } diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index f6b6e9356..25d9fce88 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -2683,8 +2683,8 @@ $$ let n = 100 let nums = randomNumbers(n: n) let index = findOne(nums: nums) - print("数组 [ 1, 2, ..., n ] 被打乱后 =", nums) - print("数字 1 的索引为", index) + print("数组 [ 1, 2, ..., n ] 被打乱后 = \(nums)") + print("数字 1 的索引为 \(index)") } } ``` From cb0071924e98bbf593d172d684acc54a492749c8 Mon Sep 17 00:00:00 2001 From: reanon <793584285@qq.com> Date: Mon, 9 Jan 2023 01:05:21 +0800 Subject: [PATCH 18/21] feat(tree/avl_tree): add go code --- codes/go/chapter_tree/avl_tree.go | 212 +++++++++++++++++++++++++ codes/go/chapter_tree/avl_tree_test.go | 54 +++++++ codes/go/pkg/tree_node.go | 14 +- 3 files changed, 274 insertions(+), 6 deletions(-) create mode 100644 codes/go/chapter_tree/avl_tree.go create mode 100644 codes/go/chapter_tree/avl_tree_test.go diff --git a/codes/go/chapter_tree/avl_tree.go b/codes/go/chapter_tree/avl_tree.go new file mode 100644 index 000000000..111abd722 --- /dev/null +++ b/codes/go/chapter_tree/avl_tree.go @@ -0,0 +1,212 @@ +// File: avl_tree.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import . "github.com/krahets/hello-algo/pkg" + +/* AVL Tree*/ +type avlTree struct { + // 根节点 + root *TreeNode +} + +func newAVLTree() *avlTree { + return &avlTree{root: nil} +} + +/* 获取结点高度 + */ +func height(node *TreeNode) int { + // 空结点高度为 -1 ,叶结点高度为 0 + if node != nil { + return node.Height + } + return -1 +} + +/* 更新结点高度 */ +func updateHeight(node *TreeNode) { + lh := height(node.Left) + rh := height(node.Right) + // 结点高度等于最高子树高度 + 1 + if lh > rh { + node.Height = lh + 1 + } else { + node.Height = rh + 1 + } +} + +/* 获取平衡因子 */ +func balanceFactor(node *TreeNode) int { + // 空结点平衡因子为 0 + if node == nil { + return 0 + } + // 结点平衡因子 = 左子树高度 - 右子树高度 + return height(node.Left) - height(node.Right) +} + +/* 右旋操作 */ +func rightRotate(node *TreeNode) *TreeNode { + child := node.Left + grandChild := child.Right + // 以 child 为原点,将 node 向右旋转 + child.Right = node + node.Left = grandChild + // 更新结点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child +} + +/* 左旋操作 */ +func leftRotate(node *TreeNode) *TreeNode { + child := node.Right + grandChild := child.Left + // 以 child 为原点,将 node 向左旋转 + child.Left = node + node.Right = grandChild + // 更新结点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child +} + +/* 执行旋转操作,使该子树重新恢复平衡 */ +func rotate(node *TreeNode) *TreeNode { + // 获取结点 node 的平衡因子 + // Go 推荐短变量,这里 bf 指代 balanceFactor + bf := balanceFactor(node) + // 左偏树 + if bf > 1 { + if balanceFactor(node.Left) >= 0 { + // 右旋 + return rightRotate(node) + } else { + // 先左旋后右旋 + node.Left = leftRotate(node.Left) + return rightRotate(node) + } + } + // 右偏树 + if bf < -1 { + if balanceFactor(node.Right) <= 0 { + // 左旋 + return leftRotate(node) + } else { + // 先右旋后左旋 + node.Right = rightRotate(node.Right) + return leftRotate(node) + } + } + // 平衡树,无需旋转,直接返回 + return node +} + +/* 插入结点 */ +func (t *avlTree) insert(val int) *TreeNode { + t.root = insertHelper(t.root, val) + return t.root +} + +/* 递归插入结点(辅助函数) */ +func insertHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return NewTreeNode(val) + } + /* 1. 查找插入位置,并插入结点 */ + if val < node.Val { + node.Left = insertHelper(node.Left, val) + } else if val > node.Val { + node.Right = insertHelper(node.Right, val) + } else { + // 重复结点不插入,直接返回 + return node + } + // 更新结点高度 + updateHeight(node) + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node +} + +/* 删除结点 */ +func (t *avlTree) remove(val int) *TreeNode { + root := removeHelper(t.root, val) + return root +} + +/* 递归删除结点(辅助函数) */ +func removeHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return nil + } + /* 1. 查找结点,并删除之 */ + if val < node.Val { + node.Left = removeHelper(node.Left, val) + } else if val > node.Val { + node.Right = removeHelper(node.Right, val) + } else { + if node.Left == nil || node.Right == nil { + child := node.Left + if node.Right != nil { + child = node.Right + } + // 子结点数量 = 0 ,直接删除 node 并返回 + if child == nil { + return nil + } else { + // 子结点数量 = 1 ,直接删除 node + node = child + } + } else { + // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 + temp := getInOrderNext(node.Right) + node.Right = removeHelper(node.Right, temp.Val) + node.Val = temp.Val + } + } + // 更新结点高度 + updateHeight(node) + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node +} + +/* 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) */ +func getInOrderNext(node *TreeNode) *TreeNode { + if node == nil { + return node + } + // 循环访问左子结点,直到叶结点时为最小结点,跳出 + for node.Left != nil { + node = node.Left + } + return node +} + +/* 查找结点 */ +func (t *avlTree) search(val int) *TreeNode { + cur := t.root + // 循环查找,越过叶结点后跳出 + for cur != nil { + // 目标结点在 root 的右子树中 + if cur.Val < val { + cur = cur.Right + } else if cur.Val > val { + // 目标结点在 root 的左子树中 + cur = cur.Left + } else { + // 找到目标结点,跳出循环 + break + } + } + // 返回目标结点 + return cur +} diff --git a/codes/go/chapter_tree/avl_tree_test.go b/codes/go/chapter_tree/avl_tree_test.go new file mode 100644 index 000000000..c4fc6b719 --- /dev/null +++ b/codes/go/chapter_tree/avl_tree_test.go @@ -0,0 +1,54 @@ +// File: avl_tree_test.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestAVLTree(t *testing.T) { + /* 初始化空 AVL 树 */ + tree := newAVLTree() + /* 插入结点 */ + // 请关注插入结点后,AVL 树是如何保持平衡的 + testInsert(tree, 1) + testInsert(tree, 2) + testInsert(tree, 3) + testInsert(tree, 4) + testInsert(tree, 5) + testInsert(tree, 8) + testInsert(tree, 7) + testInsert(tree, 9) + testInsert(tree, 10) + testInsert(tree, 6) + + /* 插入重复结点 */ + testInsert(tree, 7) + + /* 删除结点 */ + // 请关注删除结点后,AVL 树是如何保持平衡的 + testRemove(tree, 8) // 删除度为 0 的结点 + testRemove(tree, 5) // 删除度为 1 的结点 + testRemove(tree, 4) // 删除度为 2 的结点 + + /* 查询结点 */ + node := tree.search(7) + fmt.Printf("\n查找到的结点对象为 %#v ,结点值 = %d \n", node, node.Val) +} + +func testInsert(tree *avlTree, val int) { + tree.insert(val) + fmt.Printf("\n插入结点 %d 后,AVL 树为 \n", val) + PrintTree(tree.root) +} + +func testRemove(tree *avlTree, val int) { + tree.remove(val) + fmt.Printf("\n删除结点 %d 后,AVL 树为 \n", val) + PrintTree(tree.root) +} diff --git a/codes/go/pkg/tree_node.go b/codes/go/pkg/tree_node.go index b1e630e67..2ac2e22d1 100644 --- a/codes/go/pkg/tree_node.go +++ b/codes/go/pkg/tree_node.go @@ -9,16 +9,18 @@ import ( ) type TreeNode struct { - Val int - Left *TreeNode - Right *TreeNode + Val int // 结点值 + Height int // 结点高度 + Left *TreeNode // 左子结点引用 + Right *TreeNode // 右子结点引用 } func NewTreeNode(v int) *TreeNode { return &TreeNode{ - Left: nil, - Right: nil, - Val: v, + Val: v, + Height: 0, + Left: nil, + Right: nil, } } From 388509a84261c5690a511df8888d251cd7f151a9 Mon Sep 17 00:00:00 2001 From: reanon <793584285@qq.com> Date: Mon, 9 Jan 2023 01:12:19 +0800 Subject: [PATCH 19/21] docs(tree/avl_tree): add go code --- codes/go/chapter_tree/avl_tree.go | 3 +- docs/chapter_tree/avl_tree.md | 167 ++++++++++++++++++++++++++++-- 2 files changed, 161 insertions(+), 9 deletions(-) diff --git a/codes/go/chapter_tree/avl_tree.go b/codes/go/chapter_tree/avl_tree.go index 111abd722..e83b2882b 100644 --- a/codes/go/chapter_tree/avl_tree.go +++ b/codes/go/chapter_tree/avl_tree.go @@ -16,8 +16,7 @@ func newAVLTree() *avlTree { return &avlTree{root: nil} } -/* 获取结点高度 - */ +/* 获取结点高度 */ func height(node *TreeNode) int { // 空结点高度为 -1 ,叶结点高度为 0 if node != nil { diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index 43e0d9f45..8fc2313c7 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -60,7 +60,13 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Go" ```go title="avl_tree.go" - + /* AVL 树结点类 */ + type TreeNode struct { + Val int // 结点值 + Height int // 结点高度 + Left *TreeNode // 左子结点引用 + Right *TreeNode // 右子结点引用 + } ``` === "JavaScript" @@ -143,7 +149,26 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Go" ```go title="avl_tree.go" - + /* 获取结点高度 */ + func height(node *TreeNode) int { + // 空结点高度为 -1 ,叶结点高度为 0 + if node != nil { + return node.Height + } + return -1 + } + + /* 更新结点高度 */ + func updateHeight(node *TreeNode) { + lh := height(node.Left) + rh := height(node.Right) + // 结点高度等于最高子树高度 + 1 + if lh > rh { + node.Height = lh + 1 + } else { + node.Height = rh + 1 + } + } ``` === "JavaScript" @@ -225,7 +250,15 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "Go" ```go title="avl_tree.go" - + /* 获取平衡因子 */ + func balanceFactor(node *TreeNode) int { + // 空结点平衡因子为 0 + if node == nil { + return 0 + } + // 结点平衡因子 = 左子树高度 - 右子树高度 + return height(node.Left) - height(node.Right) + } ``` === "JavaScript" @@ -338,7 +371,19 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "Go" ```go title="avl_tree.go" - + /* 右旋操作 */ + func rightRotate(node *TreeNode) *TreeNode { + child := node.Left + grandChild := child.Right + // 以 child 为原点,将 node 向右旋转 + child.Right = node + node.Left = grandChild + // 更新结点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child + } ``` === "JavaScript" @@ -441,7 +486,19 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "Go" ```go title="avl_tree.go" - + /* 左旋操作 */ + func leftRotate(node *TreeNode) *TreeNode { + child := node.Right + grandChild := child.Left + // 以 child 为原点,将 node 向左旋转 + child.Left = node + node.Right = grandChild + // 更新结点高度 + updateHeight(node) + updateHeight(child) + // 返回旋转后子树的根节点 + return child + } ``` === "JavaScript" @@ -592,7 +649,36 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "Go" ```go title="avl_tree.go" - + /* 执行旋转操作,使该子树重新恢复平衡 */ + func rotate(node *TreeNode) *TreeNode { + // 获取结点 node 的平衡因子 + // Go 推荐短变量,这里 bf 指代 balanceFactor + bf := balanceFactor(node) + // 左偏树 + if bf > 1 { + if balanceFactor(node.Left) >= 0 { + // 右旋 + return rightRotate(node) + } else { + // 先左旋后右旋 + node.Left = leftRotate(node.Left) + return rightRotate(node) + } + } + // 右偏树 + if bf < -1 { + if balanceFactor(node.Right) <= 0 { + // 左旋 + return leftRotate(node) + } else { + // 先右旋后左旋 + node.Right = rightRotate(node.Right) + return leftRotate(node) + } + } + // 平衡树,无需旋转,直接返回 + return node + } ``` === "JavaScript" @@ -730,7 +816,32 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "Go" ```go title="avl_tree.go" - + /* 插入结点 */ + func (t *avlTree) insert(val int) *TreeNode { + t.root = insertHelper(t.root, val) + return t.root + } + /* 递归插入结点(辅助函数) */ + func insertHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return NewTreeNode(val) + } + /* 1. 查找插入位置,并插入结点 */ + if val < node.Val { + node.Left = insertHelper(node.Left, val) + } else if val > node.Val { + node.Right = insertHelper(node.Right, val) + } else { + // 重复结点不插入,直接返回 + return node + } + // 更新结点高度 + updateHeight(node) + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node + } ``` === "JavaScript" @@ -876,7 +987,49 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "Go" ```go title="avl_tree.go" + /* 删除结点 */ + func (t *avlTree) remove(val int) *TreeNode { + root := removeHelper(t.root, val) + return root + } + /* 递归删除结点(辅助函数) */ + func removeHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return nil + } + /* 1. 查找结点,并删除之 */ + if val < node.Val { + node.Left = removeHelper(node.Left, val) + } else if val > node.Val { + node.Right = removeHelper(node.Right, val) + } else { + if node.Left == nil || node.Right == nil { + child := node.Left + if node.Right != nil { + child = node.Right + } + // 子结点数量 = 0 ,直接删除 node 并返回 + if child == nil { + return nil + } else { + // 子结点数量 = 1 ,直接删除 node + node = child + } + } else { + // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 + temp := getInOrderNext(node.Right) + node.Right = removeHelper(node.Right, temp.Val) + node.Val = temp.Val + } + } + // 更新结点高度 + updateHeight(node) + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node) + // 返回子树的根节点 + return node + } ``` === "JavaScript" From 4d0143613827ec426b058aaa21648de023ce7641 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Mon, 9 Jan 2023 02:30:10 +0800 Subject: [PATCH 20/21] Remove avl_tree.cpp to match the latest docs. --- codes/cpp/chapter_tree/avl_tree.cpp | 228 ---------------------------- 1 file changed, 228 deletions(-) delete mode 100644 codes/cpp/chapter_tree/avl_tree.cpp diff --git a/codes/cpp/chapter_tree/avl_tree.cpp b/codes/cpp/chapter_tree/avl_tree.cpp deleted file mode 100644 index 8bc0741dc..000000000 --- a/codes/cpp/chapter_tree/avl_tree.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/** - * File: avl_tree.cpp - * Created Time: 2022-12-2 - * Author: mgisr (maguagua0706@gmail.com) - */ - -#include "../include/include.hpp" - -class AvlTree { -private: - TreeNode *root{}; - static bool isBalance(const TreeNode *p); - static int getBalanceFactor(const TreeNode *p); - static void updateHeight(TreeNode *p); - void fixBalance(TreeNode *p); - static bool isLeftChild(const TreeNode *p); - static TreeNode *&fromParentTo(TreeNode *node); -public: - AvlTree() = default; - AvlTree(const AvlTree &p) = default; - const TreeNode *search(int val); - bool insert(int val); - bool remove(int val); - void printTree(); -}; - -// 判断该结点是否平衡 -bool AvlTree::isBalance(const TreeNode *p) { - int balance_factor = getBalanceFactor(p); - if (-1 <= balance_factor && balance_factor <= 1) { return true; } - else { return false; } -} - -// 获取当前结点的平衡因子 -int AvlTree::getBalanceFactor(const TreeNode *p) { - if (p->left == nullptr && p->right == nullptr) { return 0; } - else if (p->left == nullptr) { return (-1 - p->right->height); } - else if (p->right == nullptr) { return p->left->height + 1; } - else { return p->left->height - p->right->height; } -} - -// 更新结点高度 -void AvlTree::updateHeight(TreeNode *p) { - if (p->left == nullptr && p->right == nullptr) { p->height = 0; } - else if (p->left == nullptr) { p->height = p->right->height + 1; } - else if (p->right == nullptr) { p->height = p->left->height + 1; } - else { p->height = std::max(p->left->height, p->right->height) + 1; } -} - -void AvlTree::fixBalance(TreeNode *p) { - // 左旋操作 - auto rotate_left = [&](TreeNode *node) -> TreeNode * { - TreeNode *temp = node->right; - temp->parent = p->parent; - node->right = temp->left; - if (temp->left != nullptr) { - temp->left->parent = node; - } - temp->left = node; - node->parent = temp; - updateHeight(node); - updateHeight(temp); - return temp; - }; - // 右旋操作 - auto rotate_right = [&](TreeNode *node) -> TreeNode * { - TreeNode *temp = node->left; - temp->parent = p->parent; - node->left = temp->right; - if (temp->right != nullptr) { - temp->right->parent = node; - } - temp->right = node; - node->parent = temp; - updateHeight(node); - updateHeight(temp); - return temp; - }; - // 根据规则选取旋转方式 - if (getBalanceFactor(p) > 1) { - if (getBalanceFactor(p->left) > 0) { - if (p->parent == nullptr) { root = rotate_right(p); } - else { fromParentTo(p) = rotate_right(p); } - } else { - p->left = rotate_left(p->left); - if (p->parent == nullptr) { root = rotate_right(p); } - else { fromParentTo(p) = rotate_right(p); } - } - } else { - if (getBalanceFactor(p->right) < 0) { - if (p->parent == nullptr) { root = rotate_left(p); } - else { fromParentTo(p) = rotate_left(p); } - } else { - p->right = rotate_right(p->right); - if (p->parent == nullptr) { root = rotate_left(p); } - else { fromParentTo(p) = rotate_left(p); } - } - } -} - -// 判断当前结点是否为其父节点的左孩子 -bool AvlTree::isLeftChild(const TreeNode *p) { - if (p->parent == nullptr) { return false; } - return (p->parent->left == p); -} - -// 返回父节点指向当前结点指针的引用 -TreeNode *&AvlTree::fromParentTo(TreeNode *node) { - if (isLeftChild(node)) { return node->parent->left; } - else { return node->parent->right; } -} - -const TreeNode *AvlTree::search(int val) { - TreeNode *p = root; - while (p != nullptr) { - if (p->val == val) { return p; } - else if (p->val > val) { p = p->left; } - else { p = p->right; } - } - return nullptr; -} - -bool AvlTree::insert(int val) { - TreeNode *p = root; - if (p == nullptr) { - root = new TreeNode(val); - return true; - } - for (;;) { - if (p->val == val) { return false; } - else if (p->val > val) { - if (p->left == nullptr) { - p->left = new TreeNode(val, p); - break; - } else { - p = p->left; - } - } else { - if (p->right == nullptr) { - p->right = new TreeNode(val, p); - break; - } else { - p = p->right; - } - } - } - for (; p != nullptr; p = p->parent) { - if (!isBalance(p)) { - fixBalance(p); - break; - } else { updateHeight(p); } - } - return true; -} - -bool AvlTree::remove(int val) { - TreeNode *p = root; - if (p == nullptr) { return false; } - while (p != nullptr) { - if (p->val == val) { - TreeNode *real_delete_node = p; - TreeNode *next_node; - if (p->left == nullptr) { - next_node = p->right; - if (p->parent == nullptr) { root = next_node; } - else { fromParentTo(p) = next_node; } - } else if (p->right == nullptr) { - next_node = p->left; - if (p->parent == nullptr) { root = next_node; } - else { fromParentTo(p) = next_node; } - } else { - while (real_delete_node->left != nullptr) { - real_delete_node = real_delete_node->left; - } - std::swap(p->val, real_delete_node->val); - next_node = real_delete_node->right; - if (real_delete_node->parent == p) { p->right = next_node; } - else { real_delete_node->parent->left = next_node; } - } - if (next_node != nullptr) { - next_node->parent = real_delete_node->parent; - } - for (p = real_delete_node; p != nullptr; p = p->parent) { - if (!isBalance(p)) { fixBalance(p); } - updateHeight(p); - } - delete real_delete_node; - return true; - } else if (p->val > val) { - p = p->left; - } else { - p = p->right; - } - } - return false; -} - -void inOrder(const TreeNode *root) { - if (root == nullptr) return; - inOrder(root->left); - cout << root->val << ' '; - inOrder(root->right); -} - -void AvlTree::printTree() { - inOrder(root); - cout << endl; -} - -int main() { - AvlTree tree = AvlTree(); - // tree.insert(13); - // tree.insert(24); - // tree.insert(37); - // tree.insert(90); - // tree.insert(53); - - tree.insert(53); - tree.insert(90); - tree.insert(37); - tree.insert(24); - tree.insert(13); - tree.remove(90); - tree.printTree(); - const TreeNode *p = tree.search(37); - cout << p->val; - return 0; -} \ No newline at end of file From 97ee638d31a0f88b13644ff0c81352094d18496f Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Mon, 9 Jan 2023 02:49:34 +0800 Subject: [PATCH 21/21] Update the Optional alias of Python codes. --- codes/python/chapter_tree/avl_tree.py | 22 +++++++++---------- .../python/chapter_tree/binary_search_tree.py | 16 ++++++-------- codes/python/chapter_tree/binary_tree_bfs.py | 4 +--- codes/python/chapter_tree/binary_tree_dfs.py | 8 +++---- codes/python/include/__init__.py | 2 +- docs/chapter_tree/avl_tree.md | 16 +++++++------- docs/chapter_tree/binary_search_tree.md | 6 ++--- docs/chapter_tree/binary_tree_traversal.md | 8 +++---- 8 files changed, 37 insertions(+), 45 deletions(-) diff --git a/codes/python/chapter_tree/avl_tree.py b/codes/python/chapter_tree/avl_tree.py index 04218e46a..83929981d 100644 --- a/codes/python/chapter_tree/avl_tree.py +++ b/codes/python/chapter_tree/avl_tree.py @@ -5,30 +5,28 @@ Author: a16su (lpluls001@gmail.com) """ import sys, os.path as osp -import typing - sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * class AVLTree: - def __init__(self, root: typing.Optional[TreeNode] = None): + def __init__(self, root: Optional[TreeNode] = None): self.root = root """ 获取结点高度 """ - def height(self, node: typing.Optional[TreeNode]) -> int: + def height(self, node: Optional[TreeNode]) -> int: # 空结点高度为 -1 ,叶结点高度为 0 if node is not None: return node.height return -1 """ 更新结点高度 """ - def __update_height(self, node: TreeNode): + def __update_height(self, node: Optional[TreeNode]): # 结点高度等于最高子树高度 + 1 node.height = max([self.height(node.left), self.height(node.right)]) + 1 """ 获取平衡因子 """ - def balance_factor(self, node: TreeNode) -> int: + def balance_factor(self, node: Optional[TreeNode]) -> int: # 空结点平衡因子为 0 if node is None: return 0 @@ -36,7 +34,7 @@ class AVLTree: return self.height(node.left) - self.height(node.right) """ 右旋操作 """ - def __right_rotate(self, node: TreeNode) -> TreeNode: + def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode: child = node.left grand_child = child.right # 以 child 为原点,将 node 向右旋转 @@ -49,7 +47,7 @@ class AVLTree: return child """ 左旋操作 """ - def __left_rotate(self, node: TreeNode) -> TreeNode: + def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode: child = node.right grand_child = child.left # 以 child 为原点,将 node 向左旋转 @@ -62,7 +60,7 @@ class AVLTree: return child """ 执行旋转操作,使该子树重新恢复平衡 """ - def __rotate(self, node: TreeNode) -> TreeNode: + def __rotate(self, node: Optional[TreeNode]) -> TreeNode: # 获取结点 node 的平衡因子 balance_factor = self.balance_factor(node) # 左偏树 @@ -92,7 +90,7 @@ class AVLTree: return self.root """ 递归插入结点(辅助函数)""" - def __insert_helper(self, node: typing.Optional[TreeNode], val: int) -> TreeNode: + def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode: if node is None: return TreeNode(val) # 1. 查找插入位置,并插入结点 @@ -114,7 +112,7 @@ class AVLTree: return root """ 递归删除结点(辅助函数) """ - def __remove_helper(self, node: typing.Optional[TreeNode], val: int) -> typing.Optional[TreeNode]: + def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]: if node is None: return None # 1. 查找结点,并删除之 @@ -141,7 +139,7 @@ class AVLTree: return self.__rotate(node) """ 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """ - def __get_inorder_next(self, node: typing.Optional[TreeNode]) -> typing.Optional[TreeNode]: + def __get_inorder_next(self, node: Optional[TreeNode]) -> Optional[TreeNode]: if node is None: return None # 循环访问左子结点,直到叶结点时为最小结点,跳出 diff --git a/codes/python/chapter_tree/binary_search_tree.py b/codes/python/chapter_tree/binary_search_tree.py index 7633b6288..2a8272761 100644 --- a/codes/python/chapter_tree/binary_search_tree.py +++ b/codes/python/chapter_tree/binary_search_tree.py @@ -5,20 +5,18 @@ Author: a16su (lpluls001@gmail.com) """ import sys, os.path as osp -import typing - sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * """ 二叉搜索树 """ class BinarySearchTree: - def __init__(self, nums: typing.List[int]) -> None: + def __init__(self, nums: List[int]) -> None: nums.sort() self.__root = self.build_tree(nums, 0, len(nums) - 1) """ 构建二叉搜索树 """ - def build_tree(self, nums: typing.List[int], start_index: int, end_index: int) -> typing.Optional[TreeNode]: + def build_tree(self, nums: List[int], start_index: int, end_index: int) -> Optional[TreeNode]: if start_index > end_index: return None @@ -31,11 +29,11 @@ class BinarySearchTree: return root @property - def root(self) -> typing.Optional[TreeNode]: + def root(self) -> Optional[TreeNode]: return self.__root """ 查找结点 """ - def search(self, num: int) -> typing.Optional[TreeNode]: + def search(self, num: int) -> Optional[TreeNode]: cur = self.root # 循环查找,越过叶结点后跳出 while cur is not None: @@ -51,7 +49,7 @@ class BinarySearchTree: return cur """ 插入结点 """ - def insert(self, num: int) -> typing.Optional[TreeNode]: + def insert(self, num: int) -> Optional[TreeNode]: root = self.root # 若树为空,直接提前返回 if root is None: @@ -81,7 +79,7 @@ class BinarySearchTree: return node """ 删除结点 """ - def remove(self, num: int) -> typing.Optional[TreeNode]: + def remove(self, num: int) -> Optional[TreeNode]: root = self.root # 若树为空,直接提前返回 if root is None: @@ -126,7 +124,7 @@ class BinarySearchTree: return cur """ 获取中序遍历中的下一个结点(仅适用于 root 有左子结点的情况) """ - def get_inorder_next(self, root: typing.Optional[TreeNode]) -> typing.Optional[TreeNode]: + def get_inorder_next(self, root: Optional[TreeNode]) -> Optional[TreeNode]: if root is None: return root # 循环访问左子结点,直到叶结点时为最小结点,跳出 diff --git a/codes/python/chapter_tree/binary_tree_bfs.py b/codes/python/chapter_tree/binary_tree_bfs.py index 225a8bd6e..fb3e93059 100644 --- a/codes/python/chapter_tree/binary_tree_bfs.py +++ b/codes/python/chapter_tree/binary_tree_bfs.py @@ -5,14 +5,12 @@ Author: a16su (lpluls001@gmail.com) """ import sys, os.path as osp -import typing - sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * """ 层序遍历 """ -def hier_order(root: TreeNode): +def hier_order(root: Optional[TreeNode]): # 初始化队列,加入根结点 queue = collections.deque() queue.append(root) diff --git a/codes/python/chapter_tree/binary_tree_dfs.py b/codes/python/chapter_tree/binary_tree_dfs.py index a1d46e697..b0efb14c8 100644 --- a/codes/python/chapter_tree/binary_tree_dfs.py +++ b/codes/python/chapter_tree/binary_tree_dfs.py @@ -5,8 +5,6 @@ Author: a16su (lpluls001@gmail.com) """ import sys, os.path as osp -import typing - sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * @@ -14,7 +12,7 @@ from include import * res = [] """ 前序遍历 """ -def pre_order(root: typing.Optional[TreeNode]): +def pre_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:根结点 -> 左子树 -> 右子树 @@ -23,7 +21,7 @@ def pre_order(root: typing.Optional[TreeNode]): pre_order(root=root.right) """ 中序遍历 """ -def in_order(root: typing.Optional[TreeNode]): +def in_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:左子树 -> 根结点 -> 右子树 @@ -32,7 +30,7 @@ def in_order(root: typing.Optional[TreeNode]): in_order(root=root.right) """ 后序遍历 """ -def post_order(root: typing.Optional[TreeNode]): +def post_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:左子树 -> 右子树 -> 根结点 diff --git a/codes/python/include/__init__.py b/codes/python/include/__init__.py index 31ed69ace..9d4e90bee 100644 --- a/codes/python/include/__init__.py +++ b/codes/python/include/__init__.py @@ -4,7 +4,7 @@ import queue import random import functools import collections -from typing import List +from typing import Optional, List, Dict, DefaultDict, OrderedDict, Set, Deque from .linked_list import ListNode, list_to_linked_list, linked_list_to_list, get_list_node from .binary_tree import TreeNode, list_to_tree, tree_to_list, get_tree_node from .print_util import print_matrix, print_linked_list, print_tree, print_dict \ No newline at end of file diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index 8fc2313c7..9944f29e8 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -134,14 +134,14 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```python title="avl_tree.py" """ 获取结点高度 """ - def height(self, node: typing.Optional[TreeNode]) -> int: + def height(self, node: Optional[TreeNode]) -> int: # 空结点高度为 -1 ,叶结点高度为 0 if node is not None: return node.height return -1 """ 更新结点高度 """ - def __update_height(self, node: TreeNode): + def __update_height(self, node: Optional[TreeNode]): # 结点高度等于最高子树高度 + 1 node.height = max([self.height(node.left), self.height(node.right)]) + 1 ``` @@ -239,7 +239,7 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit ```python title="avl_tree.py" """ 获取平衡因子 """ - def balance_factor(self, node: TreeNode) -> int: + def balance_factor(self, node: Optional[TreeNode]) -> int: # 空结点平衡因子为 0 if node is None: return 0 @@ -355,7 +355,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ```python title="avl_tree.py" """ 右旋操作 """ - def __right_rotate(self, node: TreeNode) -> TreeNode: + def __right_rotate(self, node: Optional[TreeNode]) -> TreeNode: child = node.left grand_child = child.right # 以 child 为原点,将 node 向右旋转 @@ -470,7 +470,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ```python title="avl_tree.py" """ 左旋操作 """ - def __left_rotate(self, node: TreeNode) -> TreeNode: + def __left_rotate(self, node: Optional[TreeNode]) -> TreeNode: child = node.right grand_child = child.left # 以 child 为原点,将 node 向左旋转 @@ -621,7 +621,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 ```python title="avl_tree.py" """ 执行旋转操作,使该子树重新恢复平衡 """ - def __rotate(self, node: TreeNode) -> TreeNode: + def __rotate(self, node: Optional[TreeNode]) -> TreeNode: # 获取结点 node 的平衡因子 balance_factor = self.balance_factor(node) # 左偏树 @@ -796,7 +796,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 return self.root """ 递归插入结点(辅助函数)""" - def __insert_helper(self, node: typing.Optional[TreeNode], val: int) -> TreeNode: + def __insert_helper(self, node: Optional[TreeNode], val: int) -> TreeNode: if node is None: return TreeNode(val) # 1. 查找插入位置,并插入结点 @@ -957,7 +957,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 return root """ 递归删除结点(辅助函数) """ - def __remove_helper(self, node: typing.Optional[TreeNode], val: int) -> typing.Optional[TreeNode]: + def __remove_helper(self, node: Optional[TreeNode], val: int) -> Optional[TreeNode]: if node is None: return None # 1. 查找结点,并删除之 diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index e3ddfaaac..a4c5a5d2a 100644 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -83,7 +83,7 @@ comments: true ```python title="binary_search_tree.py" """ 查找结点 """ - def search(self, num: int) -> typing.Optional[TreeNode]: + def search(self, num: int) -> Optional[TreeNode]: cur = self.root # 循环查找,越过叶结点后跳出 while cur is not None: @@ -265,7 +265,7 @@ comments: true ```python title="binary_search_tree.py" """ 插入结点 """ - def insert(self, num: int) -> typing.Optional[TreeNode]: + def insert(self, num: int) -> Optional[TreeNode]: root = self.root # 若树为空,直接提前返回 if root is None: @@ -560,7 +560,7 @@ comments: true ```python title="binary_search_tree.py" """ 删除结点 """ - def remove(self, num: int) -> typing.Optional[TreeNode]: + def remove(self, num: int) -> Optional[TreeNode]: root = self.root # 若树为空,直接提前返回 if root is None: diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index c64077072..650a15297 100644 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -66,7 +66,7 @@ comments: true ```python title="binary_tree_bfs.py" """ 层序遍历 """ - def hier_order(root: TreeNode): + def hier_order(root: Optional[TreeNode]): # 初始化队列,加入根结点 queue = collections.deque() queue.append(root) @@ -277,7 +277,7 @@ comments: true ```python title="binary_tree_dfs.py" """ 前序遍历 """ - def pre_order(root: typing.Optional[TreeNode]): + def pre_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:根结点 -> 左子树 -> 右子树 @@ -286,7 +286,7 @@ comments: true pre_order(root=root.right) """ 中序遍历 """ - def in_order(root: typing.Optional[TreeNode]): + def in_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:左子树 -> 根结点 -> 右子树 @@ -295,7 +295,7 @@ comments: true in_order(root=root.right) """ 后序遍历 """ - def post_order(root: typing.Optional[TreeNode]): + def post_order(root: Optional[TreeNode]): if root is None: return # 访问优先级:左子树 -> 右子树 -> 根结点