1. 算法概述
快速排序(Quick Sort)是由英国计算机科学家 Tony Hoare 于 1959 年提出的一种高效排序算法,也是目前应用最广泛的排序算法之一。它基于分治(Divide and Conquer)思想,通过一趟排序将待排记录分割成独立的两部分,其中一部分的关键字均比另一部分的关键字小,然后分别对这两部分继续进行排序,以达到整个序列有序的目的。
快速排序的平均时间复杂度为O(n log n),最坏情况下为O(n²),空间复杂度为O(log n)(递归栈开销)。由于其内部循环可以在大多数实际架构上高效运行,快速排序通常比其他 O(n log n) 算法更快,这也是 Java 的Arrays.sort()和 C 标准库qsort()都采用它的原因。
2. 算法原理
快速排序的核心操作是分区(Partition)。其基本步骤如下:
- 选择基准:从待排序序列中选取一个元素作为基准值(pivot)。
- 分区操作:将序列重新排列,所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准后面(相等的元素可以放在任意一边)。经过这一步,基准值就处于其最终位置。
- 递归排序:递归地对基准值左右两侧的子序列重复上述步骤,直到子序列长度为 0 或 1,此时整个序列已经有序。
分区过程图解
以序列[5, 3, 8, 4, 2]为例,选择最后一个元素2作为基准:
3. 代码实现
3.1 基础实现(MATLAB)
MATLAB 数组下标从 1 开始,且没有内置swap函数,可用arr([i j]) = arr([j i])一行完成交换。将以下代码保存为quickSortDemo.m即可运行:
functionquickSortDemo()arr=[53842716];arr=quickSort(arr,1,length(arr));disp('排序结果:');disp(arr);% 输出: 1 2 3 4 5 6 7 8endfunctionarr=quickSort(arr,low,high)iflow<high[arr,pivotIndex]=partition(arr,low,high);arr=quickSort(arr,low,pivotIndex-1);arr=quickSort(arr,pivotIndex+1,high);endendfunction[arr,pivotIndex]=partition(arr,low,high)pivot=arr(high);% 选择最后一个元素作为基准i=low-1;% 小于基准的元素的边界forj=low:high-1ifarr(j)<=pivoti=i+1;arr([ij])=arr([ji]);% 交换endendarr([i+1high])=arr([highi+1]);% 基准归位pivotIndex=i+1;end3.2 优化版本(三数取中 + 插入排序)
当序列接近有序时,基础实现会退化为 O(n²)。通过三数取中选择基准和小区间插入排序可以显著优化性能。MATLAB 版本如下(保存为quickSortOptimizedDemo.m):
functionquickSortOptimizedDemo()arr=[53842716];arr=quickSort(arr,1,length(arr));disp('排序结果:');disp(arr);endfunctionarr=quickSort(arr,low,high)INSERTION_THRESHOLD=7;% 小区间使用插入排序ifhigh-low<=INSERTION_THRESHOLD arr=insertionSort(arr,low,high);return;end[arr,pivotIndex]=partition(arr,low,high);arr=quickSort(arr,low,pivotIndex-1);arr=quickSort(arr,pivotIndex+1,high);endfunction[arr,pivotIndex]=partition(arr,low,high)% 三数取中:low、mid、high 三个位置的中值作为基准mid=low+floor((high-low)/2);ifarr(mid)<arr(low),arr([low mid])=arr([mid low]);endifarr(high)<arr(low),arr([low high])=arr([high low]);endifarr(high)<arr(mid),arr([mid high])=arr([high mid]);endarr([mid high-1])=arr([high-1mid]);% 将基准藏到 high-1pivot=arr(high-1);i=low;j=high-1;whiletruei=i+1;whilearr(i)<pivot,i=i+1;endj=j-1;whilej>low&&arr(j)>pivot,j=j-1;endifi>=j,break;endarr([ij])=arr([ji]);endarr([ihigh-1])=arr([high-1i]);pivotIndex=i;endfunctionarr=insertionSort(arr,low,high)fori=low+1:high key=arr(i);j=i-1;whilej>=low&&arr(j)>keyarr(j+1)=arr(j);j=j-1;endarr(j+1)=key;endend4. 复杂度分析
| 指标 | 最好情况 | 平均情况 | 最坏情况 |
|---|---|---|---|
| 时间复杂度 | O(n log n) | O(n log n) | O(n²) |
| 空间复杂度 | O(log n) | O(log n) | O(n) |
| 稳定性 | 不稳定 | 不稳定 | 不稳定 |
- 最好/平均情况:每次分区都能将序列均匀分割,递归深度为 log n,每层需要 O(n) 的比较,总复杂度 O(n log n)。
- 最坏情况:每次分区都极度不平衡(如序列已有序且固定选首元素),递归深度为 n,退化为 O(n²)。
- 稳定性:快速排序是不稳定排序,因为分区过程中元素的相对顺序可能被打乱。
5. 案例分析
案例:对成绩单进行排序
假设有一个学生成绩数组,需要按分数从低到高排序。MATLAB 中可用元胞数组(cell array)存放学生姓名,配合分数数组进行排序(保存为scoreSorterDemo.m):
functionscoreSorterDemo()names={'张三','李四','王五','赵六','孙七'};scores=[8592786588];[scores,idx]=quickSort(scores,1,length(scores));names=names(idx);% 按排序后的索引重排姓名disp('排序结果:');fork=1:length(names)fprintf('%s: %d\n',names{k},scores(k));endendfunction[arr,idx]=quickSort(arr,low,high)iflow<high[arr,idx,pivotIndex]=partition(arr,low,high);[arr,idx]=quickSort(arr,low,pivotIndex-1);[arr,idx]=quickSort(arr,pivotIndex+1,high);endendfunction[arr,idx,pivotIndex]=partition(arr,low,high)pivot=arr(high);i=low-1;forj=low:high-1ifarr(j)<=pivoti=i+1;arr([ij])=arr([ji]);endendarr([i+1high])=arr([highi+1]);pivotIndex=i+1;idx=1:length(arr);% 初始索引end6. 总结
快速排序凭借其优秀的平均性能和广泛的应用场景,成为算法学习中的必修内容。掌握其分治思想和分区操作是理解算法的关键。在实际工程中,通常会结合三数取中、随机化基准、小区间插入排序等优化手段来避免最坏情况的发生。建议读者动手实现一遍,并尝试用不同语言(如 Python、C++)复现,以加深理解。