快速排序(Quicksort)由托尼·霍尔在1960年提出,是一种分治策略的高效排序算法,其核心思想是通过选择基准元素将数组分为两部分,递归地对两部分进行排序。以下是详细的实现思路和示例说明:
基本原理
- 分治法(Divide and Conquer):
- 分解:选择一个基准元素(pivot),将数组分为两部分,使得左边部分的元素都小于等于基准值,右边部分的元素都大于等于基准值。
- 解决:递归地对左右两部分分别进行快速排序。
- 合并:由于左右两部分已经有序且基准元素位于正确位置,无需额外合并操作。
实现步骤
- 选择基准元素(Pivot):从数组中选择一个元素作为基准值。常见的选择方法有:
- 选择第一个元素。
- 选择最后一个元素。
- 选择中间元素。
- 随机选择或三数取中法(选择首、中、尾三个元素的中间值)。
- 分区操作(Partition):
- 将基准元素放到数组末尾(如果选择最后一个元素为基准,则无需移动)。
- 使用双指针法,将小于基准的元素交换到左边,大于基准的元素留在右边。
- 最后将基准元素放到正确的位置(左边部分的末尾)。
- 递归排序:对左右两部分分别重复上述步骤,直到子数组长度为1或0。
示例演示
假设我们要对数组 [5, 3, 8, 4, 6] 进行升序排序,选择最后一个元素 6 作为基准:
- 初始状态:
[5, 3, 8, 4, 6](基准=6) - 分区操作:
- 左指针
i从0开始,右指针j从0到倒数第二个元素遍历。 j=0:5 < 6,交换5和5,i=1,数组:[5, 3, 8, 4, 6]j=1:3 < 6,交换3和3,i=2,数组:[5, 3, 8, 4, 6]j=2:8 > 6,不变,数组:[5, 3, 8, 4, 6]j=3:4 < 6,交换8和4,i=3,数组:[5, 3, 4, 8, 6]- 最后交换基准
6和8,数组:[5, 3, 4, 6, 8]
- 左指针
- 递归排序:
- 左子数组
[5, 3, 4](基准=4)→ 排序后:[3, 4, 5] - 右子数组
[8](无需排序)
- 左子数组
- 最终结果:
[3, 4, 5, 6, 8]
代码实现
以下是快速排序的Python实现(选择最后一个元素为基准):
def quick_sort(arr):
if len(arr) <= 1:
return arr
else:
pivot = arr[-1]
left = [x for x in arr[:-1] if x <= pivot]
right = [x for x in arr[:-1] if x > pivot]
return quick_sort(left) + [pivot] + quick_sort(right)
# 测试
arr = [5, 3, 8, 4, 6]
print("排序前:", arr)
print("排序后:", quick_sort(arr))
原地分区版本(节省空间):
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
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, high):
if low < high:
pi = partition(arr, low, high)
quick_sort_inplace(arr, low, pi - 1)
quick_sort_inplace(arr, pi + 1, high)
# 测试
arr = [5, 3, 8, 4, 6]
quick_sort_inplace(arr, 0, len(arr) - 1)
print("排序后:", arr)
复杂度分析
- 时间复杂度:
- 最好情况:每次分区均匀,时间复杂度为 $O(n \log n)$。
- 平均情况:时间复杂度为 $O(n \log n)$。
- 最坏情况:每次分区极不均匀(如已排序数组选择第一个元素为基准),时间复杂度为 $O(n^2)$。
- 空间复杂度:
- 非原地版本:$O(n)$(递归栈深度)。
- 原地版本:$O(\log n)$(递归栈深度,平均情况)。
- 稳定性:快速排序是不稳定的,因为分区过程中可能交换相等元素的顺序。
优化策略
- 随机选择基准:避免最坏情况的概率,提高算法稳定性。
- 三数取中法:选择首、中、尾三个元素的中间值作为基准。
- 小数组使用插入排序:当子数组长度较小时(如小于10),使用插入排序更高效。
- 三路快排:将数组分为小于、等于、大于基准的三部分,适用于包含大量重复元素的数据。
适用场景
- 大数据集:快速排序在平均情况下效率极高,适用于大规模数据排序。
- 原地排序:原地版本只需 $O(\log n)$ 空间,适合内存有限的场景。
- 通用排序:标准库(如Python的
sorted())常采用优化后的快速排序。
对比其他排序
- 比插入排序快:对于大规模数据,快速排序的 $O(n \log n)$ 明显优于插入排序的 $O(n^2)$。
- 比归并排序省空间:原地快排空间复杂度更低($O(\log n)$ vs $O(n)$)。
- 比堆排序快:快速排序的常数因子更小,实际性能更优。
快速排序因其高效性和灵活性,成为最常用的排序算法之一。合理选择基准和优化策略能进一步提升其性能。