快速排序是一种基于分治法的高效排序算法,其平均时间复杂度为 O(n log n)。
🔍 快速排序的核心思想与步骤
快速排序的核心思想是分而治之,通过一趟排序将待排序列分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据小,然后再按此方法对这两部分数据分别进行快速排序,整个过程可以递归进行。
其具体步骤如下:
- 选择基准值:从数组中选择一个元素作为“基准”(pivot)。常见的策略有选择第一个元素、最后一个元素、中间元素,或者使用更复杂的如“三数取中法”。
- 分区操作:这是快速排序的核心。重新排列数组,使得所有比基准值小的元素都放在基准前面,所有比基准值大的元素都放在基准后面。操作结束后,基准值就处于其最终的正确位置上。
- 递归排序:递归地将小于基准值的子数组和大于基准值的子数组进行快速排序。
递归的基本情况是子数组的大小为0或1,此时数组已经被视为有序。
💻 快速排序的Java实现
以下是一个使用最后一个元素作为基准值的快速排序Java实现,代码中包含了详细的注释以帮助你理解。
public class QuickSort {
// 快速排序的公开入口方法
public static void quickSort(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
// 递归排序的主方法
private static void quickSort(int[] arr, int low, int high) {
// 递归终止条件:当子数组只有一个元素或没有元素时
if (low < high) {
// partition方法对数组进行分区,并返回基准值的正确位置索引
int pivotIndex = partition(arr, low, high);
// 递归排序基准值左边的子数组
quickSort(arr, low, pivotIndex - 1);
// 递归排序基准值右边的子数组
quickSort(arr, pivotIndex + 1, high);
}
}
// 分区方法:将数组分为两部分,并返回基准值的最终位置
private static int partition(int[] arr, int low, int high) {
// 选择最右边的元素作为基准值(pivot)
int pivot = arr[high];
// 初始化一个指针`i`,指向比基准值小的区域的最后一个元素
int i = low - 1;
// 遍历从`low`到`high-1`的所有元素
for (int j = low; j < high; j++) {
// 如果当前元素小于或等于基准值
if (arr[j] <= pivot) {
// 将指针`i`向右移动一位,扩大小于基准值的区域
i++;
// 交换arr[i]和arr[j],将当前小的元素挪到“小区域”
swap(arr, i, j);
}
}
// 将基准值(arr[high])与`i+1`位置的元素交换,将其放到正确的位置
// 此时,基准值左边的元素都小于等于它,右边的元素都大于它
swap(arr, i + 1, high);
// 返回基准值的最终位置索引
return i + 1;
}
// 交换数组中两个元素的方法
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("排序前:" + java.util.Arrays.toString(arr));
quickSort(arr);
System.out.println("排序后:" + java.util.Arrays.toString(arr));
}
}
🔄 代码执行示例
以数组 [10, 7, 8, 9, 1, 5]为例,详细说明第一轮分区过程:
-
基准值:选择最后一个元素
5作为基准。 -
分区过程:
-
基准归位:遍历结束,交换arr[i+1](即arr[1]=7)和基准arr[5]=5。数组变为
[1, 5, 8, 9, 10, 7]。此时基准值5已经位于其正确的位置(索引1)。 -
接着,递归排序左子数组
[1]和右子数组[8, 9, 10, 7]。
⚙️ 算法特性与优化
-
时间复杂度:
- 平均情况:O(n log n),高效是其被广泛使用的原因。
- 最坏情况:当数组已完全有序或逆序,且基准选择不当时,会退化为 O(n²)。
-
空间复杂度:主要取决于递归调用栈的深度,平均为 O(log n),最坏为 O(n)。
-
稳定性:快速排序是不稳定的排序算法。在分区过程中,相等元素的相对位置可能会改变。
为了避免最坏情况的发生,常见的优化策略有:
- 随机化基准:随机选择一个元素作为基准。
- 三数取中法:取数组头、尾、中间三个元素的中位数作为基准。
- 小数组优化:当子数组规模较小(如小于10)时,切换使用插入排序。
💎 核心要点回顾
快速排序的核心在于分区操作,通过双指针(如上述代码中的 i和 j)将数组划分为三个部分:小于基准区、待处理区、大于基准区。掌握好分区过程,就掌握了快速排序的精髓。