快速排序(Quick Sort)是一种基于分治思想的高效排序算法,其核心步骤包括选择基准(pivot)、分区(partition)、递归排序。以下是Java中快速排序的实现及详细解析:
1. 快速排序原理
- 选择基准:从数组中选择一个元素作为基准(通常选择最后一个元素)。
- 分区:将数组分为两部分,使得左边部分的元素均小于等于基准,右边部分的元素均大于基准。
- 递归排序:对左右两部分递归调用快速排序。
2. Java实现代码
java
public class QuickSort {
// 主方法:对外接口
public static void sort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
// 递归分治
private static void quickSort(int[] arr, int left, int right) {
if (left < right) {
int pivotIndex = partition(arr, left, right); // 获取基准位置
quickSort(arr, left, pivotIndex - 1); // 递归排序左半部分
quickSort(arr, pivotIndex + 1, right); // 递归排序右半部分
}
}
// 分区操作(Lomuto分区方案)
private static int partition(int[] arr, int left, int right) {
int pivot = arr[right]; // 选择最右元素作为基准
int i = left; // i是小于基准的边界
for (int j = left; j < right; j++) {
if (arr[j] <= pivot) {
swap(arr, i, j); // 将小于等于基准的元素交换到左边
i++;
}
}
swap(arr, i, right); // 将基准放到正确位置
return i; // 返回基准的最终索引
}
// 交换数组元素
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {10, 7, 8, 9, 1, 5};
System.out.println("排序前: " + Arrays.toString(arr));
sort(arr);
System.out.println("排序后: " + Arrays.toString(arr));
}
}
3. 关键点解析
- 基准选择 :
- 常用方法:选择最右元素(如上例)、随机选择或三数取中(避免最坏情况)。
- 分区方案 :
- Lomuto分区:简单但效率较低(如上述代码)。
- Hoare分区:更高效,但实现稍复杂(见下文优化部分)。
- 稳定性 :
- 快速排序是不稳定的(交换操作可能改变相等元素的顺序)。
- 时间复杂度 :
- 最优/平均:(O(n \log n))(每次分区平衡)。
- 最差:(O(n^2))(每次分区极度不平衡,如数组已有序)。
- 空间复杂度 :
- (O(\log n))(递归栈开销)。
4. 优化方向
(1) 三数取中法选基准
避免最坏情况(如数组已有序):
java
private static int choosePivot(int[] arr, int left, int right) {
int mid = left + (right - left) / 2;
// 对left、mid、right排序,返回中位数
if (arr[left] > arr[mid]) swap(arr, left, mid);
if (arr[left] > arr[right]) swap(arr, left, right);
if (arr[mid] > arr[right]) swap(arr, mid, right);
return mid; // 返回中位数的索引
}
(2) Hoare分区方案
减少交换次数:
java
private static int partitionHoare(int[] arr, int left, int right) {
int pivot = arr[left]; // 选择最左元素作为基准
int i = left - 1;
int j = right + 1;
while (true) {
do { i++; } while (arr[i] < pivot);
do { j--; } while (arr[j] > pivot);
if (i >= j) return j;
swap(arr, i, j);
}
}
(3) 小数组优化
对小规模子数组(如长度<10)切换为插入排序:
java
private static void quickSort(int[] arr, int left, int right) {
if (left < right) {
if (right - left < 10) {
insertionSort(arr, left, right);
} else {
int pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}
}
}
(4) 尾递归优化
减少递归栈深度:
java
private static void quickSort(int[] arr, int left, int right) {
while (left < right) {
int pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1); // 先处理左半部分
left = pivotIndex + 1; // 尾递归处理右半部分
}
}
5. 对比归并排序
| 特性 | 快速排序 | 归并排序 |
|---|---|---|
| 时间复杂度 | 平均 (O(n \log n)) | 稳定 (O(n \log n)) |
| 空间复杂度 | (O(\log n)) | (O(n)) |
| 稳定性 | 不稳定 | 稳定 |
| 适用场景 | 内存敏感、追求平均性能 | 大数据量、需要稳定性 |
6. 适用场景
- 内存敏感:快速排序的 (O(\log n)) 空间复杂度优于归并排序的 (O(n))。
- 平均性能优先:在随机数据中表现优异。
- 不要求稳定性:如排序基本类型数组。
总结
快速排序通过分治和分区思想实现了高效排序,但在最坏情况下性能较差。通过优化基准选择、分区方案和小数组处理,可以显著提升其鲁棒性。理解快速排序的核心在于掌握分区过程的实现细节。