目录
[2.1 什么是稳定性](#2.1 什么是稳定性)
[2.2 什么时候需要稳定性](#2.2 什么时候需要稳定性)
[2.3 各算法稳定性原因](#2.3 各算法稳定性原因)
[3.1 增长曲线对比](#3.1 增长曲线对比)
[3.2 实际运行时间参考(n=50000随机数据)](#3.2 实际运行时间参考(n=50000随机数据))
[5.1 数据规模小(n < 1000)](#5.1 数据规模小(n < 1000))
[5.2 数据规模中等(1000 < n < 50000)](#5.2 数据规模中等(1000 < n < 50000))
[5.3 数据规模大(n > 50000)](#5.3 数据规模大(n > 50000))
[5.4 特殊场景](#5.4 特殊场景)
[7.1 手写代码常考](#7.1 手写代码常考)
[7.2 概念常考](#7.2 概念常考)
[O(n²) 级(适合小数据)](#O(n²) 级(适合小数据))
[O(n log n) 级(工业标准)](#O(n log n) 级(工业标准))
[O(n) 级(特殊场景)](#O(n) 级(特殊场景))
一、十大排序算法总览
| 算法 | 分类 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
|---|---|---|---|---|---|---|
| 冒泡排序 | 交换 | O(n²) | O(n²) | O(n) | O(1) | 稳定 |
| 快速排序 | 交换 | O(n log n) | O(n²) | O(n log n) | O(log n) | 不稳定 |
| 直接插入排序 | 插入 | O(n²) | O(n²) | O(n) | O(1) | 稳定 |
| 希尔排序 | 插入 | O(n^1.3) | O(n²) | O(n log n) | O(1) | 不稳定 |
| 简单选择排序 | 选择 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
| 堆排序 | 选择 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
| 归并排序 | 归并 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
| 计数排序 | 非比较 | O(n+k) | O(n+k) | O(n+k) | O(k) | 稳定 |
| 桶排序 | 非比较 | O(n+k) | O(n²) | O(n) | O(n+k) | 稳定 |
| 基数排序 | 非比较 | O(d×(n+k)) | O(d×(n+k)) | O(d×(n+k)) | O(n+k) | 稳定 |
k表示数据范围(如计数排序中最大值与最小值的差),d表示位数(基数排序中)
二、稳定性详解
2.1 什么是稳定性
稳定排序:相等的元素在排序前后相对位置不变。
text
原数组:[(2,A), (1,B), (2,C)] // 数字为键,字母为附加信息
稳定排序后:[(1,B), (2,A), (2,C)] // 两个2的相对顺序不变
不稳定排序后:[(1,B), (2,C), (2,A)] // 顺序可能改变
2.2 什么时候需要稳定性
| 场景 | 说明 |
|---|---|
| 多关键字排序 | 先按A排序,再按B排序,稳定性能保留A的顺序 |
| 业务数据排序 | 用户列表按时间排序后,再按地区排序,同地区内时间顺序不变 |
| 多次排序 | 后续排序需要保留之前的排序结果 |
2.3 各算法稳定性原因
| 算法 | 稳定性 | 原因 |
|---|---|---|
| 冒泡排序 | 稳定 | 相邻交换,相等时不交换 |
| 插入排序 | 稳定 | 相等元素不移动 |
| 归并排序 | 稳定 | 合并时相等取左边 |
| 计数/桶/基数排序 | 稳定 | 按顺序分配收集 |
| 快速排序 | 不稳定 | 分区时可能跨过相等元素 |
| 希尔排序 | 不稳定 | 分组排序,不同组间顺序打乱 |
| 选择排序 | 不稳定 | 交换可能把后面的相等元素换到前面 |
| 堆排序 | 不稳定 | 堆化过程无法保证顺序 |
三、时间复杂度分析
3.1 增长曲线对比
text
n=1000时各算法大致比较次数:
O(n): 1000
O(n log n): 1000 × 10 = 10000
O(n²): 1000000
n=1000000时:
O(n): 1000000
O(n log n): 1000000 × 20 = 20000000
O(n²): 1000000000000(无法接受)
3.2 实际运行时间参考(n=50000随机数据)
| 算法 | 时间(ms) | 量级 |
|---|---|---|
| 快速排序 | ~8 | 最快 |
| 归并排序 | ~12 | 很快 |
| 堆排序 | ~15 | 很快 |
| 希尔排序 | ~20 | 中等 |
| 基数排序 | ~25 | 中等(依赖数据范围) |
| 冒泡/插入/选择 | ~2000+ | 慢 |
注:数据因机器而异,但相对关系类似
四、空间复杂度分析
| 算法 | 额外空间 | 是否原地 |
|---|---|---|
| 冒泡/插入/选择/希尔/快排/堆排 | O(1) | 是 |
| 归并排序 | O(n) | 否 |
| 计数排序 | O(k) | 否 |
| 桶排序 | O(n+k) | 否 |
| 基数排序 | O(n+k) | 否 |
内存受限场景:优先选择原地排序算法。
五、场景选型指南
5.1 数据规模小(n < 1000)
| 场景 | 推荐 | 理由 |
|---|---|---|
| 基本有序 | 插入排序 | 时间复杂度O(n) |
| 无特殊要求 | 插入排序 | 代码简单,稳定性好 |
| 需要稳定 | 冒泡/插入 | 稳定且简单 |
5.2 数据规模中等(1000 < n < 50000)
| 场景 | 推荐 | 理由 |
|---|---|---|
| 通用场景 | 快速排序 | 平均最快 |
| 需要稳定 | 归并排序 | O(n log n)稳定 |
| 内存紧张 | 堆排序 | 原地O(n log n) |
| 数据基本有序 | 插入排序 | 退化为O(n) |
5.3 数据规模大(n > 50000)
| 场景 | 推荐 | 理由 |
|---|---|---|
| 通用场景 | 快速排序 | 常数因子小 |
| 需要稳定 | 归并排序 | 唯一稳定O(n log n) |
| 内存极度紧张 | 堆排序 | 原地排序 |
| 数据范围小 | 计数/基数排序 | 可达到O(n) |
5.4 特殊场景
| 场景 | 推荐 | 理由 |
|---|---|---|
| 整数且范围小 | 计数排序 | O(n+k),k很小时极快 |
| 整数且范围大 | 基数排序 | O(d×n),d通常为常数 |
| 浮点数/字符串 | 快速排序/归并排序 | 通用比较排序 |
| 外部排序(磁盘) | 归并排序 | 适合多路归并 |
| 系统级排序 | 混合排序(如introsort) | 快排+堆排+插入 |
六、混合排序(工业级实现)
实际的标准库排序(C++ STL sort、Java Arrays.sort)通常采用混合策略:
-
数据量大:用快速排序
-
递归深度过大:切换为堆排序(防止最坏情况)
-
分区后子数组小:用插入排序(小数组效率高)
c
// 混合排序伪代码
void hybridSort(int arr[], int left, int right) {
if (right - left < THRESHOLD) {
insertionSort(arr, left, right);
return;
}
if (depth > MAX_DEPTH) {
heapSort(arr, left, right);
return;
}
int pivot = partition(arr, left, right);
hybridSort(arr, left, pivot - 1, depth + 1);
hybridSort(arr, pivot + 1, right, depth + 1);
}
七、面试高频考点
7.1 手写代码常考
| 算法 | 考察频率 |
|---|---|
| 快速排序 | ⭐⭐⭐⭐⭐ |
| 归并排序 | ⭐⭐⭐⭐ |
| 堆排序 | ⭐⭐⭐⭐ |
| 插入排序 | ⭐⭐⭐ |
| 冒泡排序 | ⭐⭐ |
7.2 概念常考
-
稳定性的含义及判断
-
各算法的时间/空间复杂度
-
为什么快速排序实际最快(缓存友好、常数因子小)
-
堆排序为什么不稳定
-
计数排序的局限性
八、快速对比卡片
O(n²) 级(适合小数据)
| 算法 | 一句话记忆 |
|---|---|
| 冒泡 | 两两交换,大的下沉 |
| 选择 | 每次选最小的放前面 |
| 插入 | 像打牌,插到合适位置 |
O(n log n) 级(工业标准)
| 算法 | 一句话记忆 |
|---|---|
| 快速 | 选基准,分两边,递归 |
| 归并 | 先分后合,需要额外空间 |
| 堆 | 建堆,取堆顶,再调整 |
| 希尔 | 分组插入,逐步缩小间隔 |
O(n) 级(特殊场景)
| 算法 | 一句话记忆 |
|---|---|
| 计数 | 统计频率,直接输出 |
| 桶 | 分到桶里,各桶排序 |
| 基数 | 按位分配,逐位收集 |
九、小结
| 场景 | 首选 | 备选 |
|---|---|---|
| 通用排序 | 快速排序 | 归并排序 |
| 需要稳定 | 归并排序 | 插入排序(小数据) |
| 内存紧张 | 堆排序 | 快速排序 |
| 整数范围小 | 计数排序 | 基数排序 |
| 数据基本有序 | 插入排序 | 冒泡排序 |
| 数据量极小 | 插入排序 | 冒泡排序 |
| 外部排序 | 归并排序 | - |
选型口诀:
-
小数据用插入
-
要稳定用归并
-
内存紧用堆排
-
通用场景快排
-
整数范围小计数
十、思考题
-
为什么快速排序的实际表现通常优于归并排序,即使它们的时间复杂度都是 O(n log n)?
-
堆排序是原地排序,为什么实际应用中不如快速排序常用?
-
计数排序和基数排序的时间复杂度都是 O(n),它们有什么局限性?
-
如果你需要排序一个包含100万个整数的文件,内存只有50MB,应该选择什么算法?
欢迎在评论区讨论你的答案。