一、快速排序是什么?为什么这么重要?
快速排序(Quick Sort)是目前综合性能最强的通用排序算法之一,由英国计算机科学家 Tony Hoare 在 1960 年发明。 它的平均时间复杂度为 O(n log n),而且原地排序(几乎不需要额外空间),在实际工程中被广泛使用(C++ STL 的 std::sort、Java 的 Arrays.sort 等底层大多都用快速排序 + 插入排序的混合实现)。
一句话总结快速排序的核心思想:
「选一个数当基准(pivot),把比它小的数全部放到左边,比它大的全部放到右边,然后对左右两边递归地继续做同样的事情。」
二、快速排序最形象的比喻
想象你要给一堆身高不同的小朋友排队(从矮到高):
- 随便挑一个小孩当“老大”(pivot)
- 所有其他小孩跟老大比: 比老大矮的 → 全部站到老大左边 比老大高的 → 全部站到老大右边
- 老大就站在自己应该在的位置(已经排好了!)
- 然后对左边那群人、右边那群人,分别再挑一个新老大,继续重复上面的过程
不断把大区间拆成小区间,直到每个小组只有 0 或 1 个人,就全部有序了。
下面为快速排序过程,注意不同缩进代表进展不同
假设有一个待排序的列表 [3, 6, 8, 10, 1, 2, 1],选择最后一个元素作为基准(pivot),排序过程如下:
-
初始状态:
1.列表:[3, 6, 8, 10, 1, 2, 1]。
2.基准元素:1(最后一个元素)。
-
第一轮分区:
1.将小于基准的元素放在左侧,大于基准的元素放在右侧。
2.分区后列表:[1, 1, 2, 10, 6, 8, 3]。
3.基准元素 1 的位置确定。
-
递归排序:
1.对左侧子列表 [1] 和右侧子列表 [2, 10, 6, 8, 3] 分别进行快速排序。
2.左侧子列表已经有序。
3.对右侧子列表 [2, 10, 6, 8, 3] 选择基准元素 3(最后一个元素):
1.分区后列表:[2, 3, 6, 8, 10]。 2.基准元素 3 的位置确定。4.继续递归排序右侧子列表 [6, 8, 10]:
1.选择基准元素 10(最后一个元素): 1.分区后列表:[6, 8, 10]。 2.基准元素 10 的位置确定。 2.继续递归排序左侧子列表 [6, 8]: 1.选择基准元素 8(最后一个元素): 1.分区后列表:[6, 8]。 2.基准元素 8 的位置确定。 2.继续递归排序左侧子列表 [6],已经有序。 -
最终结果:
列表完全有序:[1, 1, 2, 3, 6, 8, 10]。
三、快速排序最常用的分区方法 —— Lomuto 分区
目前教学和面试中最常用的写法是 Lomuto 分区(单指针方案):
// 返回 pivot 最终落下的位置
int partition(int arr[], int left, int right) {
int pivot = arr[right]; // 简单起见,选最右边元素做基准
int i = left - 1; // 小于等于区的右边界(初始在外面)
for (int j = left; j < right; j++) {
if (arr[j] <= pivot) { // 注意这里是 <= ,这样等于的元素也会交换到左边
i++;
// 交换,把小的元素放到 i 的位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 最后把 pivot 放到正确位置
int temp = arr[i + 1];
arr[i + 1] = arr[right];
arr[right] = temp;
return i + 1; // 返回 pivot 最终的位置
}
四、完整快速排序代码(C语言)
#include <stdio.h>
// 交换两个元素
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// Lomuto 分区方案
int partition(int arr[], int left, int right) {
int pivot = arr[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (arr[j] <= pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[right]);
return i + 1;
}
// 快速排序递归函数
void quickSort(int arr[], int left, int right) {
if (left < right) {
int pivotIndex = partition(arr, left, right);
// 分别对左右两边递归
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}
}
// 对外接口
void quick_sort(int arr[], int n) {
if (n <= 1) return;
quickSort(arr, 0, n - 1);
}
// 测试
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90, 5, 87, 1, 73, 31, 45};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前:");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
quick_sort(arr, n);
printf("排序后:");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
六、真实工程中的常见优化
- 随机化基准(防止最坏情况) 随机选择一个元素与末尾交换,再用末尾做 pivot
- 小数组切换插入排序 当区间长度 ≤ 8~16 时,改用插入排序(常数更小)
- 三路快速排序(处理大量相同元素) 把等于 pivot 的元素单独分一组
- 三数取中(median-of-three) 取 left、mid、right 中间大小的值做 pivot
七、结语
快速排序之所以强大,是因为它平均情况极快 + 原地 + 实现相对简单。 虽然最坏情况下会退化,但通过随机化、三数取中等简单优化,就能让它在绝大多数实际场景中成为最快的排序算法。 希望这篇文章 + 推荐的图片能帮助你更好地理解和讲解快速排序!