快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
前提:以下所有的算法版本都假设数组中并无重复元素。
代码如下:
public static void quickSort(int[] a, int lo, int hi) {
if (lo < hi) {
int pivot = partition(a, lo, hi);
quickSort(a, lo, pivot - 1);
quickSort(a, pivot + 1, hi);
}
}
public static int partition(int[] a, int lo, int hi) {
int x = a[lo];
int j = hi + 1;
for (int i = hi; i > lo; i--) {
if (a[i] >= x) {
j--;
swap(a, i, j);
}
}
swap(a, lo, j - 1);
return j - 1;
}
算法时间复杂度:上面算法如果在最坏情况(数组中的元素已经有序)下,时间复杂度为Θ(n^2);期望运行时间为Θ(nlogn)。
算法空间复杂度:由于快速排序为原址排序,所以其主要的空间复杂度来自于递归调用,每一次递归调用将在调用栈上创建一个栈帧。假设每一次传递数组的信息是采用指针的方式,那么每一次递归调用可以认为其空间复杂度为O(1)。那么算法在最坏情况下,有Θ(n)次递归调用,所以此时其空间复杂度为Θ(n);如果算法在平均情况下,其空间复杂度为O(logn)。
代码如下:
public static int randomized_partition(int[] a, int lo, int hi) {
int i = new Random().nextInt(hi - lo + 1) + lo;
swap(a, lo, i);
return partition(a, lo, hi);
}
这个版本的实现只是对原始版本的代码稍作改动,只不过将随机在数组中选择一个主元,并与数组中的第一个元素进行交换。上述代码中的partition方法与原始版本相同。
算法时间复杂度:虽然我们对程序在最坏情况下的运行时间感兴趣,但是在随机化版本中,它并没有改变最坏情况下的运行时间,它只是减少了出现最坏情况的可能性。因此,我们只分析算法的期望运行时间,而不是其最坏运行时间。它的期望运行时间是Θ(nlogn)。
算法空间复杂度:由于这个版本的算法只是减少最坏情况出现的可能性,所以其空间复杂度与原始版本的分析一致。
代码如下:
public static void quickSort(int[] a, int lo, int hi) {
if (lo < hi) {
int pivot = hoare_partition(a, lo, hi);
quickSort(a, lo, pivot);
quickSort(a, pivot + 1, hi);
}
}
public static int hoare_partition(int[] a, int lo, int hi) {
int x = a[lo];
int i = lo - 1;
int j = hi + 1;
while (true) {
do
j--;
while (a[j] > x);
do
i++;
while (a[i] < x);
if (i < j)
swap(a, i, j);
else
return j;
}
}
这个版本中的quickSort方法,第一行递归不是pivot - 1而是pivot。这是因为当hoare_partition结束时,只能保证a[p…j]中的每一个元素都小于或等于a[j+1…r]中的元素,其所选取的主元并没有就位。
这个版本的算法在含有许多重复元素的情况下,可以避免其出现最坏情况的划分。
算法时间复杂度:由于这个版本的算法并没有杜绝最坏情况的出现,所以分析同上面两个版本。
算法空间复杂度:由于这个版本的算法并没有杜绝最坏情况的出现,所以分析同上面两个版本。
我们知道,对于任何一种算法改进的版本来说,都不可能完全避免最坏情况的出现,它们只是减小其出现的机率。但是改变最坏情况下的空间复杂度是可能做到的。我们通过递归调用元素少的那部分,对于元素多的那部分,我们改写尾递归。只要元素少的那部分总是小于或等于输入规模的一半,那么递归调用至多为O(logn)。
代码如下:
public static void tailRecursiveQuickSort(int[] a, int lo, int hi) {
while (lo < hi) {
int pivot = partition(a, lo, hi);//partition方法与上述版本相同
if ( pivot - lo < hi - pivot ) {
quickSort(a, lo, pivot - 1);
lo = pivot + 1;
} else {
quickSort(a, pivot + 1, hi);
hi = pivot - 1;
}
}
}
算法时间复杂度:由于这个版本的算法并没有杜绝最坏情况的出现,所以分析同上面两个版本。
算法空间复杂度:这个版本的算法其递归深度至多为logn,所以其空间复杂度为O(logn);