mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-05 03:30:30 +08:00
Number the H1 and H2 headings.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 冒泡排序
|
||||
# 11.2. 冒泡排序
|
||||
|
||||
「冒泡排序 Bubble Sort」是一种最基础的排序算法,非常适合作为第一个学习的排序算法。顾名思义,「冒泡」是该算法的核心操作。
|
||||
|
||||
@@ -44,7 +44,7 @@ comments: true
|
||||
|
||||
<p align="center"> Fig. 冒泡操作 </p>
|
||||
|
||||
## 算法流程
|
||||
## 11.2.1. 算法流程
|
||||
|
||||
1. 设数组长度为 $n$ ,完成第一轮「冒泡」后,数组最大元素已在正确位置,接下来只需排序剩余 $n - 1$ 个元素。
|
||||
2. 同理,对剩余 $n - 1$ 个元素执行「冒泡」,可将第二大元素交换至正确位置,因而待排序元素只剩 $n - 2$ 个。
|
||||
@@ -232,7 +232,7 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
## 11.2.2. 算法特性
|
||||
|
||||
**时间复杂度 $O(n^2)$** :各轮「冒泡」遍历的数组长度为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,因此使用 $O(n^2)$ 时间。
|
||||
|
||||
@@ -244,7 +244,7 @@ comments: true
|
||||
|
||||
**自适应排序**:引入 `flag` 优化后(见下文),最佳时间复杂度为 $O(N)$ 。
|
||||
|
||||
## 效率优化
|
||||
## 11.2.3. 效率优化
|
||||
|
||||
我们发现,若在某轮「冒泡」中未执行任何交换操作,则说明数组已经完成排序,可直接返回结果。考虑可以增加一个标志位 `flag` 来监听该情况,若出现则直接返回。
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 插入排序
|
||||
# 11.3. 插入排序
|
||||
|
||||
「插入排序 Insertion Sort」是一种基于 **数组插入操作** 的排序算法。
|
||||
|
||||
@@ -14,7 +14,7 @@ comments: true
|
||||
|
||||
<p align="center"> Fig. 插入操作 </p>
|
||||
|
||||
## 算法流程
|
||||
## 11.3.1. 算法流程
|
||||
|
||||
1. 第 1 轮先选取数组的 **第 2 个元素** 为 `base` ,执行「插入操作」后, **数组前 2 个元素已完成排序**。
|
||||
2. 第 2 轮选取 **第 3 个元素** 为 `base` ,执行「插入操作」后, **数组前 3 个元素已完成排序**。
|
||||
@@ -194,7 +194,7 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
## 11.3.2. 算法特性
|
||||
|
||||
**时间复杂度 $O(n^2)$** :最差情况下,各轮插入操作循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和为 $\frac{(n - 1) n}{2}$ ,使用 $O(n^2)$ 时间。
|
||||
|
||||
@@ -206,7 +206,7 @@ comments: true
|
||||
|
||||
**自适应排序**:最佳情况下,时间复杂度为 $O(n)$ 。
|
||||
|
||||
## 插入排序 vs 冒泡排序
|
||||
## 11.3.3. 插入排序 vs 冒泡排序
|
||||
|
||||
!!! question
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 排序简介
|
||||
# 11.1. 排序简介
|
||||
|
||||
「排序算法 Sorting Algorithm」使得列表中的所有元素按照从小到大的顺序排列。
|
||||
|
||||
@@ -13,7 +13,7 @@ comments: true
|
||||
|
||||
<p align="center"> Fig. 排序中的不同元素类型和判断规则 </p>
|
||||
|
||||
## 评价维度
|
||||
## 11.1.1. 评价维度
|
||||
|
||||
排序算法主要可根据 **稳定性 、就地性 、自适应性 、比较类** 来分类。
|
||||
|
||||
@@ -25,22 +25,22 @@ comments: true
|
||||
假设我们有一个存储学生信息的表格,第 1, 2 列分别是姓名和年龄。那么在以下示例中,「非稳定排序」会导致输入数据的有序性丢失。因此「稳定排序」是很好的特性,**在多级排序中是必须的**。
|
||||
|
||||
```shell
|
||||
# 输入数据是按照姓名排序好的
|
||||
# (name, age)
|
||||
('A', 19)
|
||||
('B', 18)
|
||||
('C', 21)
|
||||
('D', 19)
|
||||
('E', 23)
|
||||
# 输入数据是按照姓名排序好的
|
||||
# (name, age)
|
||||
('A', 19)
|
||||
('B', 18)
|
||||
('C', 21)
|
||||
('D', 19)
|
||||
('E', 23)
|
||||
|
||||
# 假设使用非稳定排序算法按年龄排序列表,
|
||||
# 结果中 ('D', 19) 和 ('A', 19) 的相对位置改变,
|
||||
# 输入数据按姓名排序的性质丢失
|
||||
('B', 18)
|
||||
('D', 19)
|
||||
('A', 19)
|
||||
('C', 21)
|
||||
('E', 23)
|
||||
# 假设使用非稳定排序算法按年龄排序列表,
|
||||
# 结果中 ('D', 19) 和 ('A', 19) 的相对位置改变,
|
||||
# 输入数据按姓名排序的性质丢失
|
||||
('B', 18)
|
||||
('D', 19)
|
||||
('A', 19)
|
||||
('C', 21)
|
||||
('E', 23)
|
||||
```
|
||||
|
||||
### 就地性
|
||||
@@ -64,7 +64,7 @@ comments: true
|
||||
|
||||
「比较类排序」的时间复杂度最优为 $O(n \log n)$ ;而「非比较类排序」可以达到 $O(n)$ 的时间复杂度,但通用性较差。
|
||||
|
||||
## 理想排序算法
|
||||
## 11.1.2. 理想排序算法
|
||||
|
||||
- **运行快**,即时间复杂度低;
|
||||
- **稳定排序**,即排序后相等元素的相对位置不变化;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 归并排序
|
||||
# 11.5. 归并排序
|
||||
|
||||
「归并排序 Merge Sort」是算法中“分治思想”的典型体现,其有「划分」和「合并」两个阶段:
|
||||
|
||||
@@ -13,7 +13,7 @@ comments: true
|
||||
|
||||
<p align="center"> Fig. 归并排序两阶段:划分与合并 </p>
|
||||
|
||||
## 算法流程
|
||||
## 11.5.1. 算法流程
|
||||
|
||||
**「递归划分」** 从顶至底递归地 **将数组从中点切为两个子数组**,直至长度为 1 ;
|
||||
|
||||
@@ -453,7 +453,7 @@ comments: true
|
||||
- `nums` 的待合并区间为 `[left, right]` ,而因为 `tmp` 只复制了 `nums` 该区间元素,所以 `tmp` 对应区间为 `[0, right - left]` ,**需要特别注意代码中各个变量的含义**。
|
||||
- 判断 `tmp[i]` 和 `tmp[j]` 的大小的操作中,还 **需考虑当子数组遍历完成后的索引越界问题**,即 `i > leftEnd` 和 `j > rightEnd` 的情况,索引越界的优先级是最高的,例如如果左子数组已经被合并完了,那么不用继续判断,直接合并右子数组元素即可。
|
||||
|
||||
## 算法特性
|
||||
## 11.5.2. 算法特性
|
||||
|
||||
- **时间复杂度 $O(n \log n)$** :划分形成高度为 $\log n$ 的递归树,每层合并的总操作数量为 $n$ ,总体使用 $O(n \log n)$ 时间。
|
||||
- **空间复杂度 $O(n)$** :需借助辅助数组实现合并,使用 $O(n)$ 大小的额外空间;递归深度为 $\log n$ ,使用 $O(\log n)$ 大小的栈帧空间。
|
||||
@@ -461,7 +461,7 @@ comments: true
|
||||
- **稳定排序**:在合并时可保证相等元素的相对位置不变。
|
||||
- **非自适应排序**:对于任意输入数据,归并排序的时间复杂度皆相同。
|
||||
|
||||
## 链表排序 *
|
||||
## 11.5.3. 链表排序 *
|
||||
|
||||
归并排序有一个很特别的优势,用于排序链表时有很好的性能表现,**空间复杂度可被优化至 $O(1)$** ,这是因为:
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 快速排序
|
||||
# 11.4. 快速排序
|
||||
|
||||
「快速排序 Quick Sort」是一种基于“分治思想”的排序算法,速度很快、应用很广。
|
||||
|
||||
@@ -263,7 +263,7 @@ comments: true
|
||||
|
||||
哨兵划分的实质是将 **一个长数组的排序问题** 简化为 **两个短数组的排序问题**。
|
||||
|
||||
## 算法流程
|
||||
## 11.4.1. 算法流程
|
||||
|
||||
1. 首先,对数组执行一次「哨兵划分」,得到待排序的 **左子数组** 和 **右子数组**;
|
||||
2. 接下来,对 **左子数组** 和 **右子数组** 分别 **递归执行**「哨兵划分」……
|
||||
@@ -412,7 +412,7 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
## 11.4.2. 算法特性
|
||||
|
||||
**平均时间复杂度 $O(n \log n)$** :平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。
|
||||
|
||||
@@ -426,7 +426,7 @@ comments: true
|
||||
|
||||
**自适应排序**:最差情况下,时间复杂度劣化至 $O(n^2)$ 。
|
||||
|
||||
## 快排为什么快?
|
||||
## 11.4.3. 快排为什么快?
|
||||
|
||||
从命名能够看出,快速排序在效率方面一定“有两把刷子”。快速排序的平均时间复杂度虽然与「归并排序」和「堆排序」一致,但实际 **效率更高**,这是因为:
|
||||
|
||||
@@ -434,7 +434,7 @@ comments: true
|
||||
- **缓存使用效率高**:哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高。而诸如「堆排序」需要跳跃式访问元素,因此不具有此特性。
|
||||
- **复杂度的常数系数低**:在提及的三种算法中,快速排序的 **比较**、**赋值**、**交换** 三种操作的总体数量最少(类似于「插入排序」快于「冒泡排序」的原因)。
|
||||
|
||||
## 基准数优化
|
||||
## 11.4.4. 基准数优化
|
||||
|
||||
**普通快速排序在某些输入下的时间效率变差**。举个极端例子,假设输入数组是完全倒序的,由于我们选取最左端元素为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,从而 **左子数组长度为 $n - 1$、右子数组长度为 $0$** 。这样进一步递归下去,**每轮哨兵划分后的右子数组长度都为 $0$** ,分治策略失效,快速排序退化为「冒泡排序」了。
|
||||
|
||||
@@ -652,7 +652,7 @@ comments: true
|
||||
}
|
||||
```
|
||||
|
||||
## 尾递归优化
|
||||
## 11.4.5. 尾递归优化
|
||||
|
||||
**普通快速排序在某些输入下的空间效率变差**。仍然以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 0 ,那么将形成一个高度为 $n - 1$ 的递归树,此时使用的栈帧空间大小劣化至 $O(n)$ 。
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 小结
|
||||
# 11.6. 小结
|
||||
|
||||
|
||||
Reference in New Issue
Block a user