板子:快速排序(教材版 Partition)
取一个枢轴,一趟划分把比它小的放左边、比它大的放右边,枢轴就落在了最终位置。然后对左右两段分别递归。 **这里严格按教材口径写:枢轴取区间的最后一个元素,用填坑法,先从前往后走。**手算「第一趟划分后的序列」必须按这个口径。
代码
int Partition(int A[], int low, int high) { // 划分 A[low..high],返回枢轴的最终位置
int i = low, j = high;
int pivot = A[j]; // 枢轴取最后一个元素,A[j] 变成一个「坑」
while (i < j) {
while (i < j && A[i] <= pivot) i++; // 从前往后,找比枢轴大的元素
if (i < j) A[j] = A[i]; // 把它填进右边的坑,A[i] 变成新的坑
while (i < j && A[j] >= pivot) j--; // 从后往前,找比枢轴小的元素
if (i < j) A[i] = A[j]; // 把它填进左边的坑,A[j] 变成新的坑
}
A[i] = pivot; // i == j:最后剩下的坑就是枢轴的最终位置
return i;
}
void QuickSort(int A[], int low, int high) {
if (low >= high) return; // 区间里不到 2 个元素,不用排
int p = Partition(A, low, high); // 枢轴已经放到 A[p]
QuickSort(A, low, p - 1); // 左半段,不包括 p
QuickSort(A, p + 1, high); // 右半段,不包括 p
}
// 调用:QuickSort(A, 0, n - 1);复杂度:平均时间
一趟划分的手算过程
3 8 2 7 1 5,枢轴取最后一个 5:
| 步 | 动作 | 数组(_ 表示坑) |
|---|---|---|
| 0 | 取走枢轴 5 | 3 8 2 7 1 _ |
| 1 | i 往右走,停在 8(比 5 大),填进右坑 | 3 _ 2 7 1 8 |
| 2 | j 往左走,停在 1(比 5 小),填进左坑 | 3 1 2 7 _ 8 |
| 3 | i 往右走,停在 7,填进右坑 | 3 1 2 _ 7 8 |
| 4 | j 往左走,碰到 i,停下 | 3 1 2 _ 7 8 |
| 5 | 枢轴填进最后的坑 | 3 1 2 5 7 8 |
关键边界
| 代码 | 为什么 |
|---|---|
pivot = A[high],先动 i | 枢轴拿走以后,坑在右端。要从左边找一个大的元素来填这个坑,所以先让 i 往右走。如果枢轴取第一个元素,坑在左端,就要反过来先动 j |
内层循环也要写 i < j | 防止 i、j 交错或越界。i == j 说明坑已经找到了最终位置 |
A[i] <= pivot 和 A[j] >= pivot 都带等号 | 和枢轴相等的元素不移动,留在原来那一侧,这样可以少移动几次 |
if (i < j) 再填 | 内层循环如果是因为 i == j 停下的,填坑就成了自己赋给自己。教材写上这个判断,逻辑更清楚 |
最后 A[i] = pivot | 循环结束时 i == j,这个位置就是最后剩下的坑 |
递归用 p - 1 和 p + 1 | 枢轴已经在最终位置,不再参与后面的排序 |
易错点
- 口径:很多书和算竞写法是枢轴取第一个元素、先从后往前走,一趟划分后的序列会不一样。408 手算题按教材口径,完整对照见 8.3 交换排序。
- 「每一趟至少有一个元素(枢轴)到达最终位置」是判断「用的是不是快排」的依据。
- 快排的空间复杂度不是
,因为有递归栈。
链接
- 📕 返回:数据结构代码板子
- ⬅️ 上一篇:散列表线性探测
- ➡️ 下一篇:partition 的应用
- 🔗 8.3 交换排序