Skip to content

Commit f1263ab

Browse files
committed
修复部分内容表述和格式问题
1 parent 1d2ad5a commit f1263ab

File tree

61 files changed

+457
-412
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+457
-412
lines changed

docs/00_preface/00_02_data_structures_algorithms.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
---
3434

35-
通常,我们可以从**「逻辑结构」****「物理结构」**两个维度对数据结构进行分类。
35+
通常,我们可以从 **「逻辑结构」****「物理结构」** 两个维度对数据结构进行分类。
3636

3737
### 1.1 数据的逻辑结构
3838

docs/00_preface/00_03_algorithm_complexity.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def find_max(arr):
4646
在上述例子中,基本操作总共执行了 $1 + n + n + n + 1 = 3 \times n + 2$ 次,因此可以用 $f(n) = 3 \times n + 2$ 表示其操作次数。
4747

4848
时间复杂度分析如下:
49+
4950
- 当 $n$ 足够大时,$3n$ 是主要影响项,常数 $2$ 可以忽略不计。
5051
- 由于我们关注的是随规模增长的趋势,常数系数 $3$ 也可以省略。
5152
- 因此,该算法的时间复杂度为 $O(n)$。这里的 $O$ 表示渐近符号,强调 $f(n)$ 与 $n$ 成正比。
@@ -69,6 +70,7 @@ def find_max(arr):
6970
**直观理解**:$T(n) = O(f(n))$ 表示「算法的运行时间至多为 $f(n)$ 的某个常数倍」,即不会比 $f(n)$ 增长得更快。
7071

7172
> **示例**
73+
>
7274
> - 如果 $T(n) = 3 \times n^2 + 2 \times n + 1$,则 $T(n) = O(n^2)$。
7375
> - 如果 $T(n) = 2 \times n + 5$,则 $T(n) = O(n)$。
7476
> - 如果 $T(n) = 100$,则 $T(n) = O(1)$。
@@ -82,6 +84,7 @@ def find_max(arr):
8284
**直观理解**:$T(n) = \Omega(f(n))$ 表示「算法的运行时间至少不会低于 $f(n)$ 的某个常数倍」,即增长速度不慢于 $f(n)$。
8385

8486
> **示例**
87+
>
8588
> - 如果 $T(n) = 3 \times n^2 + 2 \times n + 1$,则 $T(n) = \Omega(n^2)$。
8689
> - 如果 $T(n) = 2 \times n + 5$,则 $T(n) = \Omega(n)$。
8790
> - 如果 $T(n) = n^3$,则 $T(n) = \Omega(n^2)$。
@@ -95,6 +98,7 @@ def find_max(arr):
9598
**直观理解**:$T(n) = \Theta(f(n))$ 表示「算法运行时间与 $f(n)$ 同阶」,即上下界都为 $f(n)$ 的常数倍。
9699

97100
> **示例**
101+
>
98102
> - 如果 $T(n) = 3 \times n^2 + 2 \times n + 1$,则 $T(n) = \Theta(n^2)$。
99103
> - 如果 $T(n) = 2 \times n + 5$,则 $T(n) = \Theta(n)$。
100104
> - 如果 $T(n) = n \log n + n$,则 $T(n) = \Theta(n \log n)$。
@@ -306,8 +310,8 @@ def generate_permutations(arr):
306310
| $O(n)$ | 10 | 100 | 1000 | 线性搜索、数组遍历 |
307311
| $O(n \log n)$ | 33 | 664 | 9966 | 快速排序、归并排序 |
308312
| $O(n^2)$ | 100 | 10000 | 1000000| 冒泡排序、选择排序 |
309-
| $O(2^n)$ | 1024 | 1.3×10^30 | 1.1×10^301 | 递归斐波那契 |
310-
| $O(n!)$ | 3628800 | 9.3×10^157 | 4.0×10^2567 | 全排列 |
313+
| $O(2^n)$ | 1024 | $1.3 \times 10^30$ | $1.1 \times 10^301$ | 递归斐波那契 |
314+
| $O(n!)$ | 3628800 | $9.3 \times 10^157$ | $4.0 \times 10^2567$ | 全排列 |
311315

312316
### 2.4 最佳、最坏、平均时间复杂度
313317

@@ -331,7 +335,7 @@ def find(nums, val):
331335
- **最坏情况**:目标值不存在,需要遍历整个数组,时间复杂度 $O(n)$
332336
- **平均情况**:假设目标值等概率出现在任意位置,平均时间复杂度 $O(n)$
333337

334-
**实际应用**:通常使用 **最坏时间复杂度** 作为算法性能的衡量标准,因为它能保证算法在任何输入下的性能上限。只有在不同情况下的时间复杂度存在量级差异时,才需要区分三种情况。
338+
> **实际应用**:通常使用 **最坏时间复杂度** 作为算法性能的衡量标准,因为它能保证算法在任何输入下的性能上限。只有在不同情况下的时间复杂度存在量级差异时,才需要区分三种情况。
335339
336340
## 3. 空间复杂度
337341

docs/01_array/01_03_array_bubble_sort.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
冒泡排序的名字来源于这个过程:就像水中的气泡从底部向上浮到水面一样,较大的元素会逐步移动到数组的末尾。
88

99
**我们使用「冒泡」的方式来模拟一下这个过程**
10+
1011
1. 将数组元素想象成大小不同的「泡泡」,值越大的元素「泡泡」越大。
1112
2. 从左到右依次比较相邻的两个元素。
1213
3. 如果左侧元素大于右侧元素,则交换位置。
@@ -98,18 +99,19 @@ class Solution:
9899
| **最坏时间复杂度** | $O(n^2)$ | 数组逆序,需要 $n$ 趟遍历 |
99100
| **平均时间复杂度** | $O(n^2)$ | 一般情况下的复杂度 |
100101
| **空间复杂度** | $O(1)$ | 原地排序,只使用常数空间 |
101-
| **稳定性** | 稳定 | 相等元素相对位置不变 |
102+
| **稳定性** | 稳定 | 相等元素相对位置不变 |
102103

103104
**适用场景**
105+
104106
- 数据量较小($n < 50$)
105107
- 数据基本有序
106108

107109
## 5. 总结
108110

109111
冒泡排序是最简单的排序算法之一,通过相邻元素比较交换实现排序。虽然实现简单,但效率较低。
110112

111-
**优点**:实现简单,稳定排序,空间复杂度低
112-
**缺点**:时间复杂度高,交换次数多
113+
- **优点**:实现简单,稳定排序,空间复杂度低
114+
- **缺点**:时间复杂度高,交换次数多
113115

114116
## 练习题目
115117

docs/01_array/01_04_array_selection_sort.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,19 @@ class Solution:
8080
| **最坏时间复杂度** | $O(n^2)$ | 无论数组状态如何,都需要 $\frac{n(n-1)}{2}$ 次比较 |
8181
| **平均时间复杂度** | $O(n^2)$ | 选择排序的时间复杂度与数据状态无关 |
8282
| **空间复杂度** | $O(1)$ | 原地排序,只使用常数空间 |
83-
| **稳定性** | 不稳定 | 交换操作可能改变相等元素的相对顺序 |
83+
| **稳定性** | 不稳定 | 交换操作可能改变相等元素的相对顺序 |
8484

8585
**适用场景**
86+
8687
- 数据量较小($n < 50$)
8788
- 对空间复杂度要求严格的场景
8889

8990
## 5. 总结
9091

9192
选择排序是一种简单直观的排序算法,通过不断选择未排序区间的最小元素来构建有序序列。
9293

93-
**优点**:实现简单,空间复杂度低,交换次数少
94-
**缺点**:时间复杂度高,不适合大规模数据
94+
- **优点**:实现简单,空间复杂度低,交换次数少
95+
- **缺点**:时间复杂度高,不适合大规模数据
9596

9697
## 练习题目
9798

docs/01_array/01_05_array_insertion_sort.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ class Solution:
5252
| **最坏时间复杂度** | $O(n^2)$ | 数组逆序,每个元素需要比较 $i-1$ 次 |
5353
| **平均时间复杂度** | $O(n^2)$ | 一般情况下的复杂度 |
5454
| **空间复杂度** | $O(1)$ | 原地排序,只使用常数空间 |
55-
| **稳定性** | 稳定 | 相等元素相对位置不变 |
55+
| **稳定性** | 稳定 | 相等元素相对位置不变 |
5656

5757
**适用场景**
58+
5859
- 数据量较小($n < 50$)
5960
- 数据基本有序
6061
- 在线排序(数据逐个到达)
@@ -63,8 +64,8 @@ class Solution:
6364

6465
插入排序是一种简单直观的排序算法,通过逐步构建有序序列实现排序。
6566

66-
**优点**:实现简单,稳定排序,空间复杂度低,对基本有序数据效率高
67-
**缺点**:时间复杂度高,不适合大规模数据
67+
- **优点**:实现简单,稳定排序,空间复杂度低,对基本有序数据效率高
68+
- **缺点**:时间复杂度高,不适合大规模数据
6869

6970

7071
## 练习题目

docs/01_array/01_06_array_shell_sort.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,17 @@ class Solution:
8484
| **最坏时间复杂度** | $O(n^2)$ | 使用普通间隔序列时 |
8585
| **平均时间复杂度** | $O(n^{1.3})$ ~ $O(n^{1.5})$ | 取决于间隔序列选择,若选取得当接近于 $O(n \log n)$ |
8686
| **空间复杂度** | $O(1)$ | 原地排序,只使用常数空间 |
87-
| **稳定性** | 不稳定 | 不同组间的相等元素可能改变相对顺序 |
87+
| **稳定性** | 不稳定 | 不同组间的相等元素可能改变相对顺序 |
8888

8989
**补充说明:**
90+
9091
- 希尔排序的时间复杂度高度依赖于间隔序列的选择。
9192
- 当采用常见的 `gap = gap // 2` 间隔序列时,排序过程大约需要 $\log_2 n$ 趟,每一趟的操作类似于分组插入排序。
9293
- 每一趟的排序时间复杂度约为 $O(n)$,但随着 gap 的减小,实际操作次数逐步减少。
9394
- 综合来看,希尔排序的整体时间复杂度通常介于 $O(n \log n)$ 和 $O(n^2)$ 之间,若间隔序列选择得当,性能可接近 $O(n \log n)$。
9495

9596
**适用场景**
97+
9698
- 中等规模数据($50 \leq n \leq 1000$)
9799
- 对插入排序的改进需求
98100
- 对稳定性要求不高的场景
@@ -101,8 +103,8 @@ class Solution:
101103

102104
希尔排序是插入排序的改进版本,通过分组排序减少数据移动次数,提高排序效率。
103105

104-
**优点**:比插入排序更快,空间复杂度低,适合中等规模数据
105-
**缺点**:时间复杂度不稳定,不稳定排序,间隔序列选择影响性能
106+
- **优点**:比插入排序更快,空间复杂度低,适合中等规模数据
107+
- **缺点**:时间复杂度不稳定,不稳定排序,间隔序列选择影响性能
106108

107109
## 练习题目
108110

docs/01_array/01_07_array_merge_sort.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,16 @@ class Solution:
7878
| **最坏时间复杂度** | $O(n \log n)$ | 无论数组状态如何,都需要 $\log n$ 次分解和 $n$ 次合并 |
7979
| **平均时间复杂度** | $O(n \log n)$ | 归并排序的时间复杂度与数据状态无关 |
8080
| **空间复杂度** | $O(n)$ | 需要额外的辅助数组来存储合并结果 |
81-
| **稳定性** | 稳定 | 合并过程中相等元素的相对顺序保持不变 |
81+
| **稳定性** | 稳定 | 合并过程中相等元素的相对顺序保持不变 |
8282

8383
**补充说明:**
84+
8485
- 归并排序采用分治策略,将数组递归地分成两半,每次分解的时间复杂度为 $O(1)$,分解次数为 $\log n$。
8586
- 合并过程的时间复杂度为 $O(n)$,因为需要遍历两个子数组的所有元素。
8687
- 总的时间复杂度为 $O(n \log n)$,这是基于比较的排序算法的理论下界。
8788

8889
**适用场景:**
90+
8991
- 大规模数据排序($n > 1000$)
9092
- 对稳定性有要求的场景
9193
- 外部排序(数据无法全部加载到内存)
@@ -95,16 +97,15 @@ class Solution:
9597

9698
归并排序是一种高效稳定的排序算法,采用分治策略将数组递归分解后合并排序。
9799

98-
**优点**
99-
- 时间复杂度稳定,始终为 $O(n \log n)$
100-
- 稳定排序,相等元素相对位置不变
101-
- 适合大规模数据排序
102-
- 可用于外部排序和链表排序
103-
104-
**缺点**
105-
- 空间复杂度较高,需要 $O(n)$ 额外空间
106-
- 对于小规模数据,常数因子较大
107-
- 不是原地排序算法
100+
- **优点**
101+
- 时间复杂度稳定,始终为 $O(n \log n)$
102+
- 稳定排序,相等元素相对位置不变
103+
- 适合大规模数据排序
104+
- 可用于外部排序和链表排序
105+
- **缺点**
106+
- 空间复杂度较高,需要 $O(n)$ 额外空间
107+
- 对于小规模数据,常数因子较大
108+
- 不是原地排序算法
108109

109110

110111
## 练习题目

docs/01_array/01_08_array_quick_sort.md

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,16 @@ class Solution:
6767
i = random.randint(low, high)
6868
# 将基准数与最低位互换
6969
nums[i], nums[low] = nums[low], nums[i]
70-
# 以最低位为基准数,然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧。最后将基准数放到正确位置上
70+
# 随机将基准数移到首位,后续进行分区操作
7171
return self.partition(nums, low, high)
7272

73-
# 哨兵划分:以第 1 位元素 nums[low] 为基准数,然后将比基准数小的元素移动到基准数左侧,将比基准数大的元素移动到基准数右侧,最后将基准数放到正确位置上
73+
# 哨兵划分法(Hoare 法):以 nums[low] 作为基准值
74+
# 左右指针分别从区间两端向中间收缩
75+
# 使比基准值小的元素都移动到基准值左侧
76+
# 使比基准值大的元素都移动到基准值右侧
77+
# 循环后将基准值放入最终的位置,并返回该位置索引
7478
def partition(self, nums: [int], low: int, high: int) -> int:
75-
pivot = nums[low] # 基准值
79+
pivot = nums[low] # 选取基准值(当前区间第一个元素)
7680
i, j = low, high
7781

7882
while i < j:
@@ -111,14 +115,16 @@ class Solution:
111115
| **最坏时间复杂度** | $O(n^2)$ | 每次选择的基准值都是极值(如已排序数组) |
112116
| **平均时间复杂度** | $O(n \log n)$ | 随机选择基准值时的期望复杂度 |
113117
| **空间复杂度** | $O(\log n)$ | 递归栈空间,最坏情况下为 $O(n)$ |
114-
| **稳定性** | 不稳定 | 交换操作可能改变相等元素的相对位置 |
118+
| **稳定性** | 不稳定 | 交换操作可能改变相等元素的相对位置 |
115119

116120
**适用场景**
121+
117122
- 大规模数据排序($n \geq 1000$)
118123
- 对平均性能要求高的场景
119124
- 数据分布相对均匀的情况
120125

121126
**优化策略**
127+
122128
- 随机选择基准值,避免最坏情况
123129
- 三数取中法选择基准值
124130
- 小数组使用插入排序
@@ -128,17 +134,16 @@ class Solution:
128134

129135
快速排序是一种高效的排序算法,采用分治策略,通过分区操作将数组分成两部分,然后递归排序。
130136

131-
**优点**
132-
- 平均情况下效率高,时间复杂度为 $O(n \log n)$
133-
- 原地排序,空间复杂度低
134-
- 缓存友好,局部性良好
135-
- 实际应用中常数因子较小
136-
137-
**缺点**
138-
- 不稳定排序
139-
- 最坏情况下性能较差,时间复杂度为 $O(n^2)$
140-
- 对于小数组,其他算法可能更快
141-
- 递归调用可能导致栈溢出
137+
- **优点**
138+
- 平均情况下效率高,时间复杂度为 $O(n \log n)$
139+
- 原地排序,空间复杂度低
140+
- 缓存友好,局部性良好
141+
- 实际应用中常数因子较小
142+
- **缺点**
143+
- 不稳定排序
144+
- 最坏情况下性能较差,时间复杂度为 $O(n^2)$
145+
- 对于小数组,其他算法可能更快
146+
- 递归调用可能导致栈溢出
142147

143148
快速排序是许多编程语言内置排序函数的实现基础,在实际应用中非常广泛。通过合理的优化策略,可以显著提高其性能和稳定性。
144149

docs/01_array/01_09_array_heap_sort.md

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def peek(self) -> int:
7373
2. **上移调整**:从新元素开始向上调整,直到满足堆的性质
7474

7575
**上移调整(Shift Up)过程**
76+
7677
- 将新插入的节点与其父节点比较
7778
- 如果新节点值大于父节点值,则交换它们
7879
- 重复此过程,直到新节点不再大于其父节点或到达根节点
@@ -140,6 +141,7 @@ def __shift_up(self, i: int):
140141
3. **下移调整**:从新的堆顶开始向下调整,直到满足堆的性质
141142

142143
**下移调整(Shift Down)过程**
144+
143145
- 将新的堆顶元素与其较大的子节点比较
144146
- 如果堆顶元素小于较大子节点,则交换它们
145147
- 重复此过程,直到堆顶元素不再小于其子节点或到达叶子节点
@@ -234,17 +236,11 @@ def __shift_down(self, i: int, n: int):
234236
堆排序分为两个主要阶段:
235237

236238
**第一阶段:构建初始大顶堆**
239+
237240
1. 将原始数组视为完全二叉树
238241
2. 从最后一个非叶子节点开始,自底向上进行下移调整
239242
3. 将数组转换为大顶堆
240243

241-
**第二阶段:重复提取最大值**
242-
1. 交换堆顶元素与当前末尾元素
243-
2. 堆长度减 $1$,末尾元素已排好序
244-
3. 对新的堆顶元素进行下移调整,恢复堆的性质
245-
4. 重复步骤 $1 \sim 3$,直到堆的大小为 $1$
246-
247-
248244
::: tabs#heapSortBuildMaxHeap
249245

250246
@tab <1>
@@ -277,6 +273,13 @@ def __shift_down(self, i: int, n: int):
277273

278274
:::
279275

276+
**第二阶段:重复提取最大值**
277+
278+
1. 交换堆顶元素与当前末尾元素
279+
2. 堆长度减 $1$,末尾元素已排好序
280+
3. 对新的堆顶元素进行下移调整,恢复堆的性质
281+
4. 重复步骤 $1 \sim 3$,直到堆的大小为 $1$
282+
280283
::: tabs#heapSortExchangeVal
281284

282285
@tab <1>
@@ -407,9 +410,10 @@ class Solution:
407410
| **最坏时间复杂度** | $O(n \log n)$ | 无论数组状态如何,都需要构建堆和提取元素 |
408411
| **平均时间复杂度** | $O(n \log n)$ | 堆排序的时间复杂度与数据状态无关 |
409412
| **空间复杂度** | $O(1)$ | 原地排序,只使用常数空间 |
410-
| **稳定性** | 不稳定 | 调整堆的过程中可能改变相等元素的相对顺序 |
413+
| **稳定性** | 不稳定 | 调整堆的过程中可能改变相等元素的相对顺序 |
411414

412415
**适用场景**
416+
413417
- 大规模数据排序
414418
- 内存受限的环境
415419
- 需要稳定时间复杂度的场景
@@ -420,23 +424,24 @@ class Solution:
420424
堆排序是一种基于堆数据结构的排序算法,利用堆的特性实现高效排序。
421425

422426
**核心思想**
427+
423428
- 将数组构建成大顶堆,堆顶元素始终是最大值
424429
- 重复取出堆顶元素并调整堆结构,最终得到有序数组
425430

426431
**算法步骤**
432+
427433
1. **构建初始堆**:将数组转换为大顶堆
428434
2. **重复提取**:交换堆顶与末尾元素,调整堆结构,逐步得到有序数组
429435

430-
**优点**
431-
- 时间复杂度稳定,始终为 $O(n \log n)$
432-
- 空间复杂度低,为 $O(1)$
433-
- 适合处理大规模数据
434-
- 原地排序,不需要额外空间
435-
436-
**缺点**
437-
- 不稳定排序
438-
- 常数因子较大,实际应用中可能比快速排序稍慢
439-
- 对缓存不友好,访问模式不够局部化
436+
- **优点**
437+
- 时间复杂度稳定,始终为 $O(n \log n)$
438+
- 空间复杂度低,为 $O(1)$
439+
- 适合处理大规模数据
440+
- 原地排序,不需要额外空间
441+
- **缺点**
442+
- 不稳定排序
443+
- 常数因子较大,实际应用中可能比快速排序稍慢
444+
- 对缓存不友好,访问模式不够局部化
440445

441446
堆排序是一种同时具备 $O(n \log n)$ 时间复杂度和 $O(1)$ 空间复杂度的比较排序算法,在内存受限或需要稳定时间复杂度的场景下具有重要价值。
442447

docs/01_array/01_10_array_counting_sort.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,10 @@ class Solution:
5656
| **最坏时间复杂度** | $O(n + k)$ | 无论数组状态如何,都需要统计和填充操作 |
5757
| **平均时间复杂度** | $O(n + k)$ | 计数排序的时间复杂度与数据状态无关 |
5858
| **空间复杂度** | $O(k)$ | 需要额外的计数数组,大小取决于数值范围 |
59-
| **稳定性** | 稳定 | 逆序填充保持相等元素的相对顺序 |
59+
| **稳定性** | 稳定 | 逆序填充保持相等元素的相对顺序 |
6060

6161
**适用场景**
62+
6263
- 整数排序
6364
- 数值范围较小($k$ 远小于 $n$)
6465
- 对稳定性有要求的场景
@@ -67,8 +68,8 @@ class Solution:
6768

6869
计数排序是一种非比较排序算法,通过统计元素频次实现排序。它特别适合数值范围较小的整数排序。
6970

70-
**优点**:时间复杂度稳定,稳定排序,适合小范围整数排序
71-
**缺点**:空间复杂度与数值范围相关,不适合大范围数值
71+
- **优点**:时间复杂度稳定,稳定排序,适合小范围整数排序
72+
- **缺点**:空间复杂度与数值范围相关,不适合大范围数值
7273

7374
## 练习题目
7475

0 commit comments

Comments
 (0)