快速排序由英国计算机科学家 Tony Hoare 于 1959 年在莫斯科国立大学访问期间发明,并于 1961 年正式发表。Hoare 当时正在研究机器翻译问题,需要一种高效的排序算法来处理词典。快速排序因其卓越的平均性能,被《Computing in Science & Engineering》期刊评为 20 世纪十大算法之一。Java 的 Arrays.sort() 对基本类型数组使用的就是双轴快速排序(Dual-Pivot Quicksort)。
快速排序采用分治策略:
分区操作是快速排序的核心。常见的分区方案有 Lomuto 分区和 Hoare 分区。
public class QuickSort {
/**
* 快速排序(入口方法)
*/
public static void sort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
/**
* 快速排序递归实现(Lomuto 分区方案)
*/
private static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pivotIndex = lomutoPartition(arr, low, high);
quickSort(arr, low, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, high);
}
}
/**
* Lomuto 分区方案
* 选择最右侧元素作为基准
*/
private static int lomutoPartition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1; // i 指向最后一个小于 pivot 的元素
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, high);
return i + 1;
}
/**
* Hoare 分区方案(通常更高效,交换次数更少)
*/
private static int hoarePartition(int[] arr, int low, int high) {
int pivot = arr[low];
int i = low - 1;
int j = high + 1;
while (true) {
// 从左向右找到第一个大于等于 pivot 的元素
do {
i++;
} while (arr[i] < pivot);
// 从右向左找到第一个小于等于 pivot 的元素
do {
j--;
} while (arr[j] > pivot);
if (i >= j) {
return j;
}
swap(arr, i, j);
}
}
/**
* 快速排序(三数取中法优化)
* 使用三数取中法选择基准,避免最坏情况
*/
public static void sortWithMedianOfThree(int[] arr) {
quickSortMedian(arr, 0, arr.length - 1);
}
private static void quickSortMedian(int[] arr, int low, int high) {
if (low < high) {
// 使用三数取中法选择基准
int mid = low + (high - low) / 2;
// 将三个数的中位数放到 low 位置
if (arr[mid] < arr[low]) swap(arr, low, mid);
if (arr[high] < arr[low]) swap(arr, low, high);
if (arr[high] < arr[mid]) swap(arr, mid, high);
swap(arr, low, mid); // 将中位数放到 low 位置
int pivotIndex = hoarePartition(arr, low, high);
quickSortMedian(arr, low, pivotIndex);
quickSortMedian(arr, pivotIndex + 1, high);
}
}
/**
* 快速排序(三向切分,适用于大量重复元素)
* Dijkstra 三向切分方案
*/
public static void sortThreeWay(int[] arr) {
quickSortThreeWay(arr, 0, arr.length - 1);
}
private static void quickSortThreeWay(int[] arr, int low, int high) {
if (low >= high) return;
int pivot = arr[low];
int lt = low; // arr[low..lt-1] < pivot
int gt = high; // arr[gt+1..high] > pivot
int i = low + 1; // arr[lt..i-1] == pivot
while (i <= gt) {
if (arr[i] < pivot) {
swap(arr, lt, i);
lt++;
i++;
} else if (arr[i] > pivot) {
swap(arr, i, gt);
gt--;
} else {
i++;
}
}
quickSortThreeWay(arr, low, lt - 1);
quickSortThreeWay(arr, gt + 1, high);
}
/**
* 快速排序(泛型版本)
*/
public static <T extends Comparable<T>> void sortGeneric(T[] arr) {
quickSortGeneric(arr, 0, arr.length - 1);
}
private static <T extends Comparable<T>> void quickSortGeneric(T[] arr, int low, int high) {
if (low < high) {
int pivotIndex = partitionGeneric(arr, low, high);
quickSortGeneric(arr, low, pivotIndex - 1);
quickSortGeneric(arr, pivotIndex + 1, high);
}
}
private static <T extends Comparable<T>> int partitionGeneric(T[] arr, int low, int high) {
T pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j].compareTo(pivot) <= 0) {
i++;
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
T temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
| 指标 | 最好情况 | 最坏情况 | 平均情况 |
|---|---|---|---|
| 时间复杂度 | O(n log n) | O(n²) | O(n log n) |
| 空间复杂度 | O(log n) | O(n) | O(log n) |
| 比较次数 | ≈ n log n | n(n-1)/2 | ≈ 2n ln n |
| 交换次数 | ≈ n log n / 6 | n(n-1)/2 | ≈ n log n / 3 |
快速排序是不稳定的排序算法(分区过程中相等元素的相对顺序可能改变)。虽然最坏情况是 O(n²),但通过随机化或三数取中等策略,最坏情况几乎不会发生。实际上,快速排序的平均性能是所有比较排序中最好的之一,这得益于其优秀的缓存局部性。
Arrays.sort() 对基本类型数组的默认算法(双轴快速排序)