快速排序(Quick Sort)详解
快速排序(Quick Sort)是由计算机科学家托尼·霍尔(Tony Hoare)于1959年提出的一种分治算法,被誉为"20世纪十大算法之一"。它以极高的平均效率著称,是实际应用中最常用的排序算法(如Python的sorted()
和C++的std::sort()
底层实现)。
1. 核心思想
快速排序采用"分而治之"(Divide and Conquer)策略:
- 选取基准(Pivot):从数组中选择一个元素作为基准值。
- 分区(Partition):将数组分为两部分:
- 左边部分的所有元素 ≤ 基准值
- 右边部分的所有元素 ≥ 基准值
- 递归排序:对左右子数组重复上述过程,直到子数组长度为1或0。
2. 算法步骤(示例)
以数组 [10, 80, 30, 90, 40, 50, 70]
为例,选择最后一个元素(70)作为基准:
初始状态
[10, 80, 30, 90, 40, 50, 70]
↑(i) ↑(pivot)
分区过程:
- 初始化
i = -1
,从左到右遍历:- 10 < 70 →
i++
,交换10和自身 →[10, 80, 30, ...]
(i=0) - 80 > 70 → 跳过
- 30 < 70 →
i++
,交换80和30 →[10, 30, 80, 90, ...]
(i=1) - 90 > 70 → 跳过
- 40 < 70 →
i++
,交换80和40 →[10, 30, 40, 90, 80, 50]
(i=2) - 50 < 70 →
i++
,交换90和50 →[10, 30, 40, 50, 80, 90]
(i=3)
- 10 < 70 →
- 最后交换
i+1
和基准 → 交换80和70 →[10, 30, 40, 50, 70, 90, 80]
结果:
左子数组 [10, 30, 40, 50]
(均≤70)
右子数组 [90, 80]
(均>70)
递归处理这两个子数组。
3. 代码实现
基础版本(Python)
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2] # 选择中间元素作为基准
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
原地分区版本(更高效)
def partition(arr, low, high):
pivot = arr[high] # 选择最后一个元素作为基准
i = low - 1 # i是小于基准的区域的边界
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1] # 将基准放到正确位置
return i + 1
def quick_sort_inplace(arr, low=0, high=None):
if high is None:
high = len(arr) - 1
if low < high:
pi = partition(arr, low, high) # 分区索引
quick_sort_inplace(arr, low, pi - 1)
quick_sort_inplace(arr, pi + 1, high)
4. 时间复杂度分析
情况 | 时间复杂度 | 说明 |
---|---|---|
最优 | O(n log n) | 每次分区都能均分数组 |
平均 | O(n log n) | 随机数据下的表现 |
最差 | O(n²) | 每次分区极度不平衡(如已排序数组) |
数学推导:
- 每次分区操作耗时O(n)。
- 理想情况下递归树高度为log n,总时间 = n × log n。
5. 空间复杂度
- 最优:O(log n)(递归栈深度,平衡分区时)。
- 最差:O(n)(极度不平衡分区时)。
6. 稳定性
- 不稳定排序:分区交换可能改变相等元素的相对顺序。
示例:[3₁, 2, 3₂, 1]
→ 选择3₂为基准时,3₁可能被交换到3₂之后。
7. 基准(Pivot)选择优化
基准的选择直接影响效率,常见策略:
- 随机选择:避免最坏情况(如
arr[random.randint(low, high)]
)。 - 三数取中法:选第一个、中间、最后一个元素的中位数。
- 双基准快排(Dual-Pivot Quick Sort):JDK的
Arrays.sort()
采用此优化。
8. 实际应用场景
- 大规模数据排序:平均效率远超插入/选择排序。
- 标准库实现:如C++的
std::sort
、Java的Arrays.sort()
。 - 需要缓存友好性:顺序访问内存,减少缓存未命中。
9. 与其他排序算法对比
算法 | 平均时间复杂度 | 最差时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
快速排序 | O(n log n) | O(n²) | O(log n) | 不稳定 |
归并排序 | O(n log n) | O(n log n) | O(n) | 稳定 |
堆排序 | O(n log n) | O(n log n) | O(1) | 不稳定 |
10. 常见问题
Q1:为什么快速排序比归并排序更快?
A1:虽然两者都是O(n log n),但快排序的常数因子更小,且是原地排序(归并排序需额外空间)。
Q2:如何避免最坏情况O(n²)?
A2:
- 随机化基准选择。
- 小数组时切换为插入排序(如JDK的混合排序策略)。
Q3:快速排序的递归会栈溢出吗?
A3:极端情况下可能(如完全逆序数组+最左基准选择),可通过尾递归优化或迭代实现避免。
11. 总结
- 核心优势:平均情况下最快的通用排序算法。
- 关键优化:合理选择基准 + 小数组切换插入排序。
- 适用场景:大规模随机数据排序(但需注意最坏情况)。