Skip to content

快速排序

快排为什么是标准库首选

同样是 O(n log n),为什么 C 标准库的 qsort、Java 的 Arrays.sort(基本类型)、C++ 的 std::sort 底层都选了快速排序,而不是归并或堆排?答案藏在常数因子里——快排的内层循环只做一次比较和一次赋值,cache 命中率也更高。

408 考研中,快速排序是排序章节考频最高的算法,选择题、填空题、算法题均可能涉及。理解其分区过程、递归结构和最坏情况是得分关键。

核心思想

快速排序的核心是分治法(Divide and Conquer)

  • 分区(Partition):从待排序序列中选一个元素作为基准(pivot),将序列划分为两部分——左边所有元素 ≤ pivot,右边所有元素 ≥ pivot
  • 递归(Recurse):对左、右两个子序列分别递归执行快速排序
  • 合并(Combine):无需额外合并操作,分区完成后序列自然有序

一趟排序的效果:pivot 被放到了最终位置,这是快速排序区别于其他排序的关键特征。

交互可视化

通过下方的交互动画,你可以逐步观察快速排序的执行过程:

加载可视化中...

操作详解

算法思路

快速排序的主框架非常简洁:

c
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);         // 排右子表
    }
}

整个算法的核心在于 Partition 函数的实现。

分区过程

第一个元素为基准(pivot),使用双指针 lowhigh 从两端向中间扫描:

  1. 将 pivot 暂存(pivot = A[low]
  2. high 从右向左找到第一个 < pivot 的元素,放到 low 位置
  3. low 从左向右找到第一个 > pivot 的元素,放到 high 位置
  4. 重复步骤 2-3,直到 low == high
  5. 将 pivot 放入 low 位置(此时 pivot 左边都 ≤ 它,右边都 ≥ 它)
c
int Partition(int A[], int low, int high) {
    int pivot = A[low];       // 取第一个元素为基准
    while (low < high) {
        while (low < high && A[high] >= pivot)
            high--;
        A[low] = A[high];     // 比 pivot 小的移到左端
        while (low < high && A[low] <= pivot)
            low++;
        A[high] = A[low];     // 比 pivot 大的移到右端
    }
    A[low] = pivot;           // pivot 放到最终位置
    return low;               // 返回 pivot 的位置
}

⚠️ 易错:Partition 中 while 条件的 >=<= 不能改成 ><,否则当序列中存在与 pivot 相等的元素时,两个指针都不会移动,导致死循环。

示例:对序列 [49, 38, 65, 97, 76, 13, 27] 执行一趟划分(pivot = 49):

步骤lowhigh动作数组状态
初始06pivot = A[0] = 49,暂存 pivot[49, 38, 65, 97, 76, 13, 27]
106high 扫描:A[6]=27 < 49,停。A[0]=A[6][27, 38, 65, 97, 76, 13, 27]
206low 扫描:A[0]=27 ≤ 49,low++[27, 38, 65, 97, 76, 13, 27]
316low 扫描:A[1]=38 ≤ 49,low++[27, 38, 65, 97, 76, 13, 27]
426low 扫描:A[2]=65 > 49,停。A[6]=A[2][27, 38, 65, 97, 76, 13, 65]
526high 扫描:A[6]=65 ≥ 49,high--[27, 38, 65, 97, 76, 13, 65]
625high 扫描:A[5]=13 < 49,停。A[2]=A[5][27, 38, 13, 97, 76, 13, 65]
725low 扫描:A[2]=13 ≤ 49,low++[27, 38, 13, 97, 76, 13, 65]
835low 扫描:A[3]=97 > 49,停。A[5]=A[3][27, 38, 13, 97, 76, 97, 65]
935high 扫描:A[5]=97 ≥ 49,high--[27, 38, 13, 97, 76, 97, 65]
1034high 扫描:A[4]=76 ≥ 49,high--[27, 38, 13, 97, 76, 97, 65]
1133low == high,退出循环。A[3] = pivot[27, 38, 13, 49, 76, 97, 65]

最终结果:pivot = 49 到达下标 3,左侧 [27, 38, 13] 均 ≤ 49,右侧 [76, 97, 65] 均 ≥ 49。

递归分析

快速排序的递归过程可以用递归树来理解:

  • 最好情况:每次 pivot 恰好将序列等分为两半,递归树是一棵平衡二叉树,树高为 log₂n,每层总共比较 n 次,因此总时间复杂度为 O(nlog₂n)
  • 最坏情况:每次 pivot 都是当前序列的最大或最小值,一侧子序列为空,递归树退化为单链,树高为 n,总时间复杂度为 O(n²)

递归调用需要栈空间来保存每层的参数:

  • 最好情况栈深度:O(log₂n)
  • 最坏情况栈深度:O(n)

最坏情况分析

最坏情况发生在**序列基本有序(正序或逆序)**时:

  • 若序列已经有序且每次取第一个元素为 pivot,则 pivot 就是最小值,左子表为空,右子表有 n-1 个元素
  • 每趟只能确定一个元素的位置,需要 n-1 趟
  • 比较次数:n-1 + n-2 + ... + 1 = n(n-1)/2 = O(n²)

⚠️ 易错:'快排最坏 O(n²)'不意味着快排不好。408 选择题常考:'对任意输入数据,以下哪种排序算法最坏情况仍为 O(n log n)?'——答案是归并和堆排,快排不在其中。

pivot 优化策略(降低最坏情况出现概率):

  1. 随机选取 pivot:从当前子序列中随机选一个元素与 A[low] 交换,再执行标准 Partition
  2. 三数取中法:取 A[low]A[mid]A[high] 三者的中值作为 pivot,有效避免有序输入导致的退化
  3. 当子序列较短时改用直接插入排序:递归到小规模子问题时,插入排序的常数因子更小,效率更高

复杂度分析

指标最好情况平均情况最坏情况说明
时间复杂度O(nlog₂n)O(nlog₂n)O(n²)平均性能最优的内部排序
空间复杂度O(log₂n)O(log₂n)O(n)递归栈的深度

其他性质

性质结论
稳定性不稳定(分区交换可能改变相同元素的相对顺序)
适用场景数据量大且基本无序时效率最高
是否原地排序是(不计递归栈空间)
每趟结果特征一趟排序确定一个元素的最终位置

⚠️ 易错:'每趟确定一个元素的最终位置'是快排的独有特征。408 真题常给出某趟排序后的序列,问'可能是以下哪种排序的结果'——如果恰好有一个元素到达了最终位置(左边都比它小,右边都比它大),就可能是快排。但注意:冒泡排序每趟也能确定一个元素(最大或最小),区别在于冒泡确定的是全局最大/最小值,快排确定的不一定是。

考研高频考点

  • ⭐ 手动模拟一趟 Partition 过程,写出划分结果(选择题/填空题必考)
  • ⭐ 快速排序的时间复杂度(最好、最坏、平均)及其推导(选择题/简答题高频)
  • ⭐ 快速排序是不稳定排序(举反例说明)
  • ⭐ 最坏情况何时发生(初始序列有序 + 取首元素为 pivot)
  • ⭐ 每趟排序至少确定一个元素的最终位置(判断某趟排序后的序列是否可能是快排的结果)
  • 快速排序的空间复杂度为 O(log₂n)~O(n),来自递归栈
  • pivot 优化策略(随机化、三数取中)
  • 快速排序与其他 O(nlog₂n) 排序(归并、堆排)的对比

相关知识

  • 快排的 Partition 思想也用于快速选择算法(Quick Select),O(n) 时间找第 k 小元素——这是 Partition 的另一个重要应用
  • 快排在子序列较短时常切换为直接插入排序,因为小规模数据下插入排序的常数因子更小
  • 快排的递归结构与归并排序形成对比:快排是"先处理再递归"(先 partition 后递归子表),归并是"先递归再处理"(先递归子表后合并)
  • 快排的空间复杂度来自递归栈,与堆排序的 O(1) 空间形成对比——这是选择排序算法时的重要权衡

真题练习

相关真题(6题)

2024Q8选择题2分

快速排序:一趟划分后 P 和 Q 两部分的有序性质判断

2023Q10选择题2分

排序稳定性:希尔排序、快速排序、堆排序均不稳定

2023Q11选择题2分

快速排序划分:一趟划分后根据左小右大性质判断枢轴元素为 81

2019Q10选择题2分

快速排序:每趟排序后基准元素到达最终位置的性质

2014Q11选择题2分

快速排序:两趟排序后仅一个元素到达最终位置

2010Q10选择题2分

快速排序:递归次数与分区处理顺序的关系