Skip to content

Commit

Permalink
Fix all the ** (bolded symbols).
Browse files Browse the repository at this point in the history
  • Loading branch information
krahets committed Jan 9, 2023
1 parent 97ee638 commit aaa2ff2
Show file tree
Hide file tree
Showing 20 changed files with 93 additions and 93 deletions.
6 changes: 3 additions & 3 deletions docs/chapter_array_and_linkedlist/array.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,9 @@ elementAddr = firtstElementAddr + elementLength * elementIndex

**数组中插入或删除元素效率低下**。假设我们想要在数组中间某位置插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。删除元素也是类似,需要把此索引之后的元素都向前移动一位。总体看有以下缺点:

- **时间复杂度高** 数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。
- **丢失元素** 由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。
- **内存浪费** 我们一般会初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是我们不关心的,但这样做同时也会造成内存空间的浪费。
- **时间复杂度高**数组的插入和删除的平均时间复杂度均为 $O(N)$ ,其中 $N$ 为数组长度。
- **丢失元素**由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会被丢失。
- **内存浪费**我们一般会初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是我们不关心的,但这样做同时也会造成内存空间的浪费。

![array_insert_remove_element](array.assets/array_insert_remove_element.png)

Expand Down
6 changes: 3 additions & 3 deletions docs/chapter_array_and_linkedlist/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,9 +599,9 @@ comments: true

为了帮助加深对列表的理解,我们在此提供一个列表的简易版本的实现。需要关注三个核心点:

- **初始容量** 选取一个合理的数组的初始容量 `initialCapacity` 。在本示例中,我们选择 10 作为初始容量。
- **数量记录** 需要声明一个变量 `size` ,用来记录列表当前有多少个元素,并随着元素插入与删除实时更新。根据此变量,可以定位列表的尾部,以及判断是否需要扩容。
- **扩容机制** 插入元素有可能导致超出列表容量,此时需要扩容列表,方法是建立一个更大的数组来替换当前数组。需要给定一个扩容倍数 `extendRatio` ,在本示例中,我们规定每次将数组扩容至之前的 2 倍。
- **初始容量**选取一个合理的数组的初始容量 `initialCapacity` 。在本示例中,我们选择 10 作为初始容量。
- **数量记录**需要声明一个变量 `size` ,用来记录列表当前有多少个元素,并随着元素插入与删除实时更新。根据此变量,可以定位列表的尾部,以及判断是否需要扩容。
- **扩容机制**插入元素有可能导致超出列表容量,此时需要扩容列表,方法是建立一个更大的数组来替换当前数组。需要给定一个扩容倍数 `extendRatio` ,在本示例中,我们规定每次将数组扩容至之前的 2 倍。

本示例是为了帮助读者对如何实现列表产生直观的认识。实际编程语言中,列表的实现远比以下代码复杂且标准,感兴趣的读者可以查阅源码学习。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ comments: true

换言之,在可以解决问题的前提下,算法效率则是主要评价维度,包括:

- **时间效率** ,即算法的运行速度的快慢。
- **空间效率** ,即算法占用的内存空间大小。
- **时间效率**,即算法的运行速度的快慢。
- **空间效率**,即算法占用的内存空间大小。

数据结构与算法追求“运行速度快、占用内存少”,而如何去评价算法效率则是非常重要的问题,因为只有知道如何评价算法,才能去做算法之间的对比分析,以及优化算法设计。

Expand All @@ -32,7 +32,7 @@ comments: true

既然实际测试具有很大的局限性,那么我们是否可以仅通过一些计算,就获知算法的效率水平呢?答案是肯定的,我们将此估算方法称为「复杂度分析 Complexity Analysis」或「渐近复杂度分析 Asymptotic Complexity Analysis」。

**复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势** 。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。
**复杂度分析评估随着输入数据量的增长,算法的运行时间和占用空间的增长趋势**。根据时间和空间两方面,复杂度可分为「时间复杂度 Time Complexity」和「空间复杂度 Space Complexity」。

**复杂度分析克服了实际测试方法的弊端**。一是独立于测试环境,分析结果适用于所有运行平台。二是可以体现不同数据量下的算法效率,尤其是可以反映大数据量下的算法性能。

Expand Down
2 changes: 1 addition & 1 deletion docs/chapter_computational_complexity/space_complexity.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ comments: true

# 空间复杂度

「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势** 。这个概念与时间复杂度很类似。
「空间复杂度 Space Complexity」统计 **算法使用内存空间随着数据量变大时的增长趋势**。这个概念与时间复杂度很类似。

## 算法相关空间

Expand Down
10 changes: 5 additions & 5 deletions docs/chapter_data_structure/classification_of_data_structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ comments: true

我们一般将逻辑结构分为「线性」和「非线性」两种。“线性”这个概念很直观,即表明数据在逻辑关系上是排成一条线的;而如果数据之间的逻辑关系是非线形的(例如是网状或树状的),那么就是非线性数据结构。

- **线性数据结构** 数组、链表、栈、队列、哈希表;
- **非线性数据结构** 树、图、堆、哈希表;
- **线性数据结构**数组、链表、栈、队列、哈希表;
- **非线性数据结构**树、图、堆、哈希表;

![classification_logic_structure](classification_of_data_structure.assets/classification_logic_structure.png)

Expand All @@ -25,16 +25,16 @@ comments: true

若感到阅读困难,建议先看完下个章节「数组与链表」,再回过头来理解物理结构的含义。

**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储****链表的离散空间存储** 。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。
**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储****链表的离散空间存储**。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。

![classification_phisical_structure](classification_of_data_structure.assets/classification_phisical_structure.png)

<p align="center"> Fig. 连续空间存储与离散空间存储 </p>

**所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。

- **基于数组可实现** 栈、队列、堆、哈希表、矩阵、张量(维度 $\geq 3$ 的数组)等;
- **基于链表可实现** 栈、队列、堆、哈希表、树、图等;
- **基于数组可实现**栈、队列、堆、哈希表、矩阵、张量(维度 $\geq 3$ 的数组)等;
- **基于链表可实现**栈、队列、堆、哈希表、树、图等;

基于数组实现的数据结构也被称为「静态数据结构」,这意味着该数据结构在在被初始化后,长度不可变。相反地,基于链表实现的数据结构被称为「动态数据结构」,该数据结构在被初始化后,我们也可以在程序运行中修改其长度。

Expand Down
2 changes: 1 addition & 1 deletion docs/chapter_data_structure/data_and_memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ comments: true

**「基本数据类型」与「数据结构」之间的联系与区别**

我们知道,数据结构是在计算机中 **组织与存储数据的方式** ,它的主语是“结构”,而不是“数据”。比如,我们想要表示“一排数字”,自然应该使用「数组」这个数据结构。数组的存储方式使之可以表示数字的相邻关系、先后关系等一系列我们需要的信息,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,**则与所谓的数据的结构无关了**
我们知道,数据结构是在计算机中 **组织与存储数据的方式**,它的主语是“结构”,而不是“数据”。比如,我们想要表示“一排数字”,自然应该使用「数组」这个数据结构。数组的存储方式使之可以表示数字的相邻关系、先后关系等一系列我们需要的信息,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,**则与所谓的数据的结构无关了**

=== "Java"

Expand Down
14 changes: 7 additions & 7 deletions docs/chapter_hashing/hash_collision.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ comments: true

在原始哈希表中,一个桶地址只能存储一个元素(即键值对)。**考虑将桶地址内的单个元素转变成一个链表,将所有冲突元素都存储在一个链表中**,此时哈希表操作方法为:

- **查询元素** 先将 key 输入到哈希函数得到桶地址(即访问链表头部),再遍历链表来确定对应的 value 。
- **添加元素** 先通过哈希函数访问链表头部,再将元素直接添加到链表头部即可。
- **删除元素** 同样先访问链表头部,再遍历链表查找对应元素,删除之即可。
- **查询元素**先将 key 输入到哈希函数得到桶地址(即访问链表头部),再遍历链表来确定对应的 value 。
- **添加元素**先通过哈希函数访问链表头部,再将元素直接添加到链表头部即可。
- **删除元素**同样先访问链表头部,再遍历链表查找对应元素,删除之即可。

(图)

Expand All @@ -46,9 +46,9 @@ comments: true

「线性探测」使用固定步长的线性查找来解决哈希冲突。

**插入元素** 如果出现哈希冲突,则从冲突位置向后线性遍历(步长一般取 1 ),直到找到一个空位,则将元素插入到该空位中。
**插入元素**如果出现哈希冲突,则从冲突位置向后线性遍历(步长一般取 1 ),直到找到一个空位,则将元素插入到该空位中。

**查找元素** 若出现哈希冲突,则使用相同步长执行线性查找,会遇到两种情况:
**查找元素**若出现哈希冲突,则使用相同步长执行线性查找,会遇到两种情况:

1. 找到对应元素,返回 value 即可;
2. 若遇到空位,则说明查找键值对不在哈希表中;
Expand All @@ -64,9 +64,9 @@ comments: true

顾名思义,「多次哈希」的思路是基于多个哈希函数 $f_1(x)$ , $f_2(x)$ , $f_3(x)$ , $\cdots$ 进行探测。

**插入元素** 若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推……直到找到空位后插入元素。
**插入元素**若哈希函数 $f_1(x)$ 出现冲突,则尝试 $f_2(x)$ ,以此类推……直到找到空位后插入元素。

**查找元素** 以相同的哈希函数顺序查找,存在两种情况:
**查找元素**以相同的哈希函数顺序查找,存在两种情况:

1. 找到目标元素,则返回之;
2. 到空位或已尝试所有哈希函数,说明哈希表中无此元素;
Expand Down
8 changes: 4 additions & 4 deletions docs/chapter_hashing/hash_map.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ comments: true

除了哈希表之外,还可以使用以下数据结构来实现上述查询功能:

1. **无序数组** 每个元素为 `[学号, 姓名]`
2. **有序数组** `1.` 中的数组按照学号从小到大排序;
3. **链表** 每个结点的值为 `[学号, 姓名]`
4. **二叉搜索树** 每个结点的值为 `[学号, 姓名]` ,根据学号大小来构建树;
1. **无序数组**每个元素为 `[学号, 姓名]`
2. **有序数组**`1.` 中的数组按照学号从小到大排序;
3. **链表**每个结点的值为 `[学号, 姓名]`
4. **二叉搜索树**每个结点的值为 `[学号, 姓名]` ,根据学号大小来构建树;

使用上述方法,各项操作的时间复杂度如下表所示(在此不做赘述,详解可见 [二叉搜索树章节](https://www.hello-algo.com/chapter_tree/binary_search_tree/#_6))。无论是查找元素、还是增删元素,哈希表的时间复杂度都是 $O(1)$ ,全面胜出!

Expand Down
6 changes: 3 additions & 3 deletions docs/chapter_preface/about_the_book.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ comments: true

首先介绍数据结构与算法的评价维度、算法效率的评估方法,引出了计算复杂度概念。

接下来,从 **函数渐近上界** 入手,分别介绍了 **时间复杂度****空间复杂度** ,包括推算方法、常见类型、示例等。同时,剖析了 **最差、最佳、平均** 时间复杂度的联系与区别。
接下来,从 **函数渐近上界** 入手,分别介绍了 **时间复杂度****空间复杂度**,包括推算方法、常见类型、示例等。同时,剖析了 **最差、最佳、平均** 时间复杂度的联系与区别。

### 数据结构

首先介绍了常用的 **基本数据类型** 、以及它们是如何在内存中存储的。

接下来,介绍了两种 **数据结构分类方法** ,包括逻辑结构与物理结构。
接下来,介绍了两种 **数据结构分类方法**,包括逻辑结构与物理结构。

后续展开介绍了 **数组、链表、栈、队列、散列表、树、堆、图** 等数据结构,关心以下内容:

Expand Down Expand Up @@ -84,7 +84,7 @@ comments: true

- 标题后标注 * 符号的是选读章节,如果你的时间有限,可以先跳过这些章节。
- 文章中的重要名词会用「」符号标注,例如「数组 Array」。名词混淆会导致不必要的歧义,因此最好可以记住这类名词(包括中文和英文),以便后续阅读文献时使用。
- 重点内容、总起句、总结句会被 **加粗** ,此类文字值得特别关注。
- 重点内容、总起句、总结句会被 **加粗**,此类文字值得特别关注。
- 专有名词和有特指含义的词句会使用 “ ” 标注,以避免歧义。
- 在工程应用中,每种语言都有注释规范;而本书放弃了一部分的注释规范性,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。

Expand Down
2 changes: 1 addition & 1 deletion docs/chapter_preface/suggestions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ git clone https://github.com/krahets/hello-algo.git

### 运行源代码

本书提供配套 Java, C++, Python 代码仓(后续可能拓展支持语言)。书中的代码栏上若标有 `*.java` , `*.cpp` , `*.py` ,则可在仓库 codes 文件夹中找到对应的 **代码源文件**
本书提供配套 Java, C++, Python 代码仓(后续可能拓展支持语言)。书中的代码栏上若标有 `*.java` , `*.cpp` , `*.py` ,则可在仓库 codes 文件夹中找到对应的 **代码源文件**

![code_md_to_repo](suggestions.assets/code_md_to_repo.png)

Expand Down
6 changes: 3 additions & 3 deletions docs/chapter_searching/binary_search.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ comments: true
使用二分查找有两个前置条件:

- **要求输入数据是有序的**,这样才能通过判断大小关系来排除一半的搜索区间;
- **二分查找仅适用于数组** ,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。
- **二分查找仅适用于数组**,而在链表中使用效率很低,因为其在循环中需要跳跃式(非连续地)访问元素。

## 算法实现

Expand Down Expand Up @@ -480,9 +480,9 @@ $$

## 复杂度分析

**时间复杂度 $O(\log n)$** 其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。
**时间复杂度 $O(\log n)$** 其中 $n$ 为数组或链表长度;每轮排除一半的区间,因此循环轮数为 $\log_2 n$ ,使用 $O(\log n)$ 时间。

**空间复杂度 $O(1)$** 指针 `i` , `j` 使用常数大小空间。
**空间复杂度 $O(1)$** 指针 `i` , `j` 使用常数大小空间。

## 优点与缺点

Expand Down
4 changes: 2 additions & 2 deletions docs/chapter_searching/hashing_search.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ comments: true

## 复杂度分析

**时间复杂度** $O(1)$哈希表的查找操作使用 $O(1)$ 时间。
**时间复杂度 $O(1)$**哈希表的查找操作使用 $O(1)$ 时间。

**空间复杂度** $O(n)$其中 $n$ 为数组或链表长度。
**空间复杂度 $O(n)$**其中 $n$ 为数组或链表长度。

## 优点与缺点

Expand Down
4 changes: 2 additions & 2 deletions docs/chapter_searching/linear_search.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,9 @@ comments: true

## 复杂度分析

**时间复杂度 $O(n)$** 其中 $n$ 为数组或链表长度。
**时间复杂度 $O(n)$** 其中 $n$ 为数组或链表长度。

**空间复杂度 $O(1)$** 无需使用额外空间。
**空间复杂度 $O(1)$** 无需使用额外空间。

## 优点与缺点

Expand Down
10 changes: 5 additions & 5 deletions docs/chapter_sorting/bubble_sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,15 @@ comments: true

## 算法特性

**时间复杂度 $O(n^2)$** 各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。
**时间复杂度 $O(n^2)$** 各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。

**空间复杂度 $O(1)$** 指针 $i$ , $j$ 使用常数大小的额外空间。
**空间复杂度 $O(1)$** 指针 $i$ , $j$ 使用常数大小的额外空间。

**原地排序** 指针变量仅使用常数大小额外空间。
**原地排序**指针变量仅使用常数大小额外空间。

**稳定排序** 不交换相等元素。
**稳定排序**不交换相等元素。

**自适排序** 引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。
**自适排序**引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。

## 效率优化

Expand Down
12 changes: 6 additions & 6 deletions docs/chapter_sorting/insertion_sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,23 +183,23 @@ comments: true

## 算法特性

**时间复杂度 $O(n^2)$** 最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。
**时间复杂度 $O(n^2)$** 最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。

**空间复杂度 $O(1)$** 指针 $i$ , $j$ 使用常数大小的额外空间。
**空间复杂度 $O(1)$** 指针 $i$ , $j$ 使用常数大小的额外空间。

**原地排序** 指针变量仅使用常数大小额外空间。
**原地排序**指针变量仅使用常数大小额外空间。

**稳定排序** 不交换相等元素。
**稳定排序**不交换相等元素。

**自适应排序** 最佳情况下,时间复杂度为 $O(n)$ 。
**自适应排序**最佳情况下,时间复杂度为 $O(n)$ 。

## 插入排序 vs 冒泡排序

!!! question

虽然「插入排序」和「冒泡排序」的时间复杂度皆为 $O(n^2)$ ,但实际运行速度却有很大差别,这是为什么呢?

回顾复杂度分析,两个方法的循环次数都是 $\frac{(n - 1) n}{2}$ 。但不同的是,「冒泡操作」是在做 **元素交换** ,需要借助一个临时变量实现,共 3 个单元操作;而「插入操作」是在做 **赋值** ,只需 1 个单元操作;因此,可以粗略估计出冒泡排序的计算开销约为插入排序的 3 倍。
回顾复杂度分析,两个方法的循环次数都是 $\frac{(n - 1) n}{2}$ 。但不同的是,「冒泡操作」是在做 **元素交换**,需要借助一个临时变量实现,共 3 个单元操作;而「插入操作」是在做 **赋值**,只需 1 个单元操作;因此,可以粗略估计出冒泡排序的计算开销约为插入排序的 3 倍。

插入排序运行速度快,并且具有原地、稳定、自适应的优点,因此很受欢迎。实际上,包括 Java 在内的许多编程语言的排序库函数的实现都用到了插入排序。库函数的大致思路:

Expand Down
Loading

0 comments on commit aaa2ff2

Please sign in to comment.