| 项目 | 值 |
|---|---|
| 核心思想 | 选枢轴 → partition 分左右 → 递归排 |
| 平均时间 | $O(n\log n)$ |
| 最坏时间 | $O(n^2)$(已排序 + 首元素作枢轴) |
| 空间 | $O(\log n)$ 递归栈,最坏 $O(n)$ |
| 稳定性 | ❌ 不稳定 |
| 考试频率 | ⭐⭐⭐⭐⭐ 必考 |
快速排序(Quick Sort)是由 Tony Hoare 在 1960 年提出的一种基于分治法的排序算法,是目前内部排序中综合性能最好的算法之一。
核心思想:从待排序序列中选取一个元素作为枢轴(pivot),通过一趟排序将序列划分为左右两个子序列——左子序列中所有元素 $\leq pivot$,右子序列中所有元素 $> pivot$。然后对左右子序列递归执行相同操作,直到整个序列有序。
快速排序的排序过程类似于构建二叉排序树(BST):每次选取一个根节点,将比它小的放左边、比它大的放右边。因此快速排序的递归树结构与 BST 高度相关。
| 性质 | 说明 |
|---|---|
| 最好时间复杂度 | $O(n\log n)$,每次 pivot 恰好将序列等分为两半 |
| 平均时间复杂度 | $O(n\log n)$,约 $1.39n\log n$ 次比较 |
| 最坏时间复杂度 | $O(n^2)$,序列本身有序或逆序,每次只减少一个元素 |
| 空间复杂度 | 最好/平均 $O(\log n)$,最坏 $O(n)$(递归栈深度) |
| 稳定性 | ❌ 不稳定 |
L[low])low 指向序列开头,high 指向序列末尾high 指向的元素 $\geq pivot$ 时,high--;否则将 L[high] 移到 low 位置low 指向的元素 $\leq pivot$ 时,low++;否则将 L[low] 移到 high 位置low == highlow == high 指向的位置,此时 pivot 已到达最终位置[low, pivot-1] 递归执行快速排序[pivot+1, high] 递归执行快速排序low >= high(子序列长度 $\leq 1$)int Partition(int A[], int low, int high) {
int pivot = A[low]; // 选取第一个元素作为枢轴
while (low < high) {
// 从右往左找比 pivot 小的元素
while (low < high && A[high] >= pivot)
high--;
A[low] = A[high];
// 从左往右找比 pivot 大的元素
while (low < high && A[low] <= pivot)
low++;
A[high] = A[low];
}
A[low] = pivot; // pivot 放到最终位置
return low;
}
void QuickSort(int A[], int low, int high) {
if (low < high) {
int pivotpos = Partition(A, low, high);
QuickSort(A, low, pivotpos - 1);
QuickSort(A, pivotpos + 1, high);
}
}
low 端扫描,当 low 和 high 相遇时,相遇位置的元素可能大于 pivot,导致划分错误。int MedianOfThree(int A[], int low, int high) {
int mid = low + (high - low) / 2;
if (A[low] > A[mid]) swap(A[low], A[mid]);
if (A[low] > A[high]) swap(A[low], A[high]);
if (A[mid] > A[high]) swap(A[mid], A[high]);
swap(A[mid], A[high-1]); // 中位数放到 high-1 位置
return A[high-1];
}
更多优化见 quick-sort-optimization。
对序列 49, 38, 65, 97, 76, 13, 27, 49* 进行快速排序(首元素作枢轴):
第一趟划分($pivot = 49$):
初始: 49 38 65 97 76 13 27 49*
↑low ↑high
从右扫描: 27 < 49, high 停在 27
27 38 65 97 76 13 [27] 49*
↑low ↑high
从左扫描: 65 > 49, low 停在 65
27 38 [65] 97 76 13 65 49*
↑low ↑high
从右扫描: 13 < 49, high 停在 13
27 38 13 97 76 [13] 65 49*
↑low ↑high
从左扫描: 97 > 49, low 停在 97
27 38 13 [97] 76 97 65 49*
↑low ↑high
low == high, 放入 pivot:
27 38 13 [49] 76 97 65 49*
↑pivot
结果:$pivot=49$ 到达最终位置,左子表 {27, 38, 13},右子表 {76, 97, 65, 49*}。
第二趟:对左子表 {27, 38, 13}($pivot=27$)和右子表 {76, 97, 65, 49*}($pivot=76$)分别划分。
继续递归直到所有子序列长度 $\leq 1$。最终排序结果:13, 27, 38, 49, 49*, 65, 76, 97。
49 排在 49* 之前看似"稳定",但这只是巧合——快排的元素跨距离搬移不保证相等元素的相对次序。| 考法 | 解题套路 |
|---|---|
| 手动模拟一趟划分过程 | 给出初始序列,写出每一趟划分后的结果 |
| 判断是否为快速排序某一趟的结果 | 已知一趟排序后有 $n$ 个元素到达最终位置 |
| 快速排序与初始序列的关系 | 有序 / 逆序时性能最差 |
| 快速排序的改进方法 | 三数取中法、随机化枢轴 |
| 快速排序与其他排序的比较 | 平均性能最好,但不稳定 |
| 递归次数与初始序列的关系 | 与初始序列和枢轴选择有关,与分区处理顺序无关 |
| 一趟排序确定的元素个数 | 每趟划分确定 1 个元素(枢轴)的最终位置 |
high 端开始扫描 → 必须先从右往左扫描,否则会出错[3, 3*, 2],排序后 3 与 3* 的相对位置可能改变。A[low]。若先从 low 端扫描,两指针相遇处的元素可能大于 pivot,导致划分错误。low(橙)与 high(绿)指针交替逼近,相遇即划分完成。↑ 以上为站内 HTML 相对链接(纯网页可浏览);本页右上「在 Obsidian 中打开」跳回源笔记。