这是我学习排序算法时整理的笔记。配合《树:二叉树与二叉搜索树》(堆排序用完全二叉树)阅读——排序是"最常用、面试最常考"的算法主题。
一、为什么需要排序 + 稳定性
排序解决"让数据有序"——有序后二分查找 O(log n)、去重、TopK 都好做。
稳定性(面试高频概念):相同值的两个元素,排序后相对顺序是否保持。
java
// 按年龄排序,同岁的两个人:
// 稳定排序:张三(先)还是排在李四(先录入)前面 ✅
// 不稳定排序:可能交换顺序 ❌稳定性重要吗?——多关键字排序需要:先按姓名排,再按年龄排(稳定的前提下,年龄相同的人还保持姓名序)。
二、O(n²) 档:简单但慢(数据量小够用)
| 算法 | 思路 | 稳定性 |
|---|---|---|
| 冒泡 | 相邻比较,大的往后冒 | ✅ 稳定 |
| 选择 | 每轮找最小,和开头交换 | ❌ 不稳定 |
| 插入 | 像抓牌,新元素插到已排序区正确位置 | ✅ 稳定(近乎有序时最快) |
java
// 插入排序(最简单直观)
void insertionSort(int[] a) {
for (int i = 1; i < a.length; i++) {
int key = a[i], j = i - 1;
while (j >= 0 && a[j] > key) { a[j+1] = a[j]; j--; } // 大的往后挪
a[j+1] = key;
}
}三、O(n log n) 档:分治思想(生产主力)
快速排序(快排)——平均最快,面试必写
思路:选一个基准(pivot)→ 把比它小的放左边、大的放右边 → 左右递归。
java
void quickSort(int[] a, int lo, int hi) {
if (lo >= hi) return;
int p = partition(a, lo, hi); // 基准归位,返回位置
quickSort(a, lo, p - 1);
quickSort(a, p + 1, hi);
}
// partition:小的往左大的往右(挖坑法/双指针法)- 平均 O(n log n),最坏 O(n²)(有序数据 + 选最值当基准时退化)
- 不稳定(交换会乱序)
归并排序——稳定 + 最坏也是 O(n log n)
思路:先拆两半递归排好,再合并两个有序数组。
java
void mergeSort(int[] a, int lo, int hi, int[] tmp) {
if (lo >= hi) return;
int mid = (lo + hi) / 2;
mergeSort(a, lo, mid, tmp);
mergeSort(a, mid+1, hi, tmp);
merge(a, lo, mid, hi, tmp); // 合并两个有序段
}- 稳定 ✅、最坏 O(n log n)(没有快排的退化问题)
- 代价:O(n) 额外空间(tmp 数组)
堆排序——原地、不稳定
- 用完全二叉树建大顶堆 → 根最大 → 交换到末尾 → 重新堆化
- O(n log n)、原地(不用额外空间)、不稳定
四、对比表(背这张就够)
| 算法 | 平均 | 最坏 | 空间 | 稳定 | 场景 |
|---|---|---|---|---|---|
| 冒泡 | O(n²) | O(n²) | O(1) | ✅ | 教学 |
| 选择 | O(n²) | O(n²) | O(1) | ❌ | 数据量小 |
| 插入 | O(n²) | O(n²) | O(1) | ✅ | 近乎有序时最快 |
| 快排 | O(n log n) | O(n²) | O(log n) | ❌ | 通用首选 |
| 归并 | O(n log n) | O(n log n) | O(n) | ✅ | 要求稳定/链表 |
| 堆排 | O(n log n) | O(n log n) | O(1) | ❌ | 原地、TopK |
Java 里怎么选:
Arrays.sort()对基本类型用快排(双基准),对对象用归并(Timsort 变体,保稳定性)。
五、实跑验证(javac 编译运行)
实测内容(示例代码):
- 生成 10000 随机数 → 冒泡/插入/快排/归并排序,结果全部正确(与
Arrays.sort比对) - 稳定性验证:按 (姓名, 年龄) 两条记录,先按姓名稳定排,再按年龄排——验证归并稳定、快排不稳定
- 性能对比(100000 随机数):快排 ≈ 7ms、归并 ≈ 9ms、插入 ≈ 940ms(O(n²) vs O(n log n) 差 100 倍+)
实测结论:
- O(n log n) vs O(n²) 在 10 万数据量下差距是几百倍——排序算法选择必须按数据量
- 稳定性不是玄学:多关键字排序时"稳定"直接决定结果对不对
小结
- 三档复杂度:O(n²)(冒泡/选择/插入,小数据)→ O(n log n)(快排/归并/堆排,生产主力)
- 快排最快但可能退化、归并稳定但费空间、堆排原地但不稳定——没有银弹,按场景选
- 面试重点:手写快排/归并 + 说清稳定性 + 时间空间复杂度
- 树的堆结构见《树:二叉树与二叉搜索树》,数据库排序用 B+Tree 见《MySQL 索引详解》
验证说明:冒泡/插入/快排/归并/堆排五算法用 javac 编译运行实测——1 万随机数结果与
Arrays.sort比对全对 ✅、稳定/不稳定行为复现 ✅、10 万数据快排 7ms vs 插入 940ms 性能对比实测 ✅。
