作为一名对技术充满热情的学习者,我一直以来都深刻地体会到知识的广度和深度。在这个不断演变的数字时代,我远非专家,而是一位不断追求进步的旅行者。通过这篇博客,我想分享我在某个领域的学习经验,与大家共同探讨、共同成长。请大家以开放的心态阅读,相信你们也会在这段知识之旅中找到启示。
前言
学习快速排序,最重要的就是学会分治思想,对于pivot的选择有很多种,这让我们产生了如何使用更加合适的pivot时代码变得更加高效,下面我们就开始谈谈快速排序。
一、什么是快速排序
快速排序是一个高效的排序算法,由C.A.R. Hoare在1960年提出。因其平均时间复杂度为O(n log n)而被广泛使用,并且还具有原地排序(不需要额外大量的内存空间)的优点。这种算法的基本思想是分治策略,通过一个称为"pivot"(中枢或枢轴)的元素来将原数组分割成两个子数组。
快速排序的基本步骤包括:
-
选择pivot(枢轴元素):
从数组中选择一个元素作为pivot。选择方法有多种,例如选择第一个元素、最后一个元素、中间的元素,或者随机选择一个元素。选择不同的pivot可能会影响算法的性能。 -
分区操作:
重新排列数组,使得所有比pivot小的元素都排在pivot的前面,而所有比pivot大的元素都排在后面。这一步结束后,pivot就位于其最终排序后的位置。 -
递归排序子数组:
递归地在pivot左侧和右侧的子数组上重复前面的步骤,直到子数组的大小缩减到1或0,此时算法结束。
在最坏的情况下,例如当输入数组已经是排序状态或逆排序状态时,每次分区只缩减一个元素,这使得快速排序的时间复杂度退化为O(n^2)。然而,通过随机选择pivot,可以降低这种最坏情况发生的概率,使得快速排序在平均情况下展现出良好性能。
快速排序通常被认为在实际应用中比其他O(n log n)排序算法,如归并排序或堆排序,更快。因为其内部循环(inner
loop)可以有效地在多种现代架构上实现。尽管如此,在选择快速排序的实现时,还应考虑数据的特点以及实际性能表现。
二、三数取中
这里的数组是 {8, 3, 1, 7, 0, 10, 2}
,我们将使用“三数取中”方法来选择pivot,这在实践中是一个比较好的折衷方法。
在“三数取中”方法中,我们将比较数组的第一个元素、中间元素和最后一个元素,然后选择这三个数的中位数作为pivot。在我们的示例数组中,这将是 8
(第一个元素),7
(中间元素),和 2
(最后一个元素),所以pivot将是 7
。
以下是Java代码演示了如何对这个数组执行快速排序:
public class QuickSortExample {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 找到pivot所在位置
int pivotIndex = partition(arr, low, high);
// 对pivot左边的数组递归排序
quickSort(arr, low, pivotIndex - 1);
// 对pivot右边的数组递归排序
quickSort(arr, pivotIndex + 1, high);
}
}
private static int partition(int[] arr, int low, int high) {
// “三数取中”作为pivot
int pivot = medianOfThree(arr, low, high);
// 将pivot移到数组末尾
swap(arr, high, medianOfThreeIndex(arr, low, high));
int border = low - 1; // 边界
for (int i = low; i < high; i++) {
// 所有小于pivot的元素移到左边
if (arr[i]