在内部排序算法中比较常用的排序算法应该算快速排序了。时间复杂度为N*lgN。然而快排算法的实现又有很多的版本。下面总结一下。
一、常规快排算法(以数组中一个元素作为旋转点)
// quicksort the subarray from a[lo] to a[hi]
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int j = partition(a, lo, hi);
sort(a, lo, j-1);
sort(a, j+1, hi);
assert isSorted(a, lo, hi);
}
划分方法如下
// partition the subarray a[lo .. hi] by returning an index j
// so that a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
private static int partition(Comparable[] a, int lo, int hi) {
int i = lo;
int j = hi + 1;
Comparable v = a[lo];
while (true) {
// find item on lo to swap
while (less(a[++i], v))
if (i == hi) break;
// find item on hi to swap
while (less(v, a[--j]))
if (j == lo) break; // redundant since a[lo] acts as sentinel
// check if pointers cross
if (i >= j) break;
exch(a, i, j);
}
// put v = a[j] into position
exch(a, lo, j);
// with a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
return j;
}
其中,划分方法中的选择点的选择对算法的性能影响比较大。基于这个原因,有些改进算法,有的选择数组的中间项作为旋转点,但这样的改进等于没改进,因为选第一个和选中间那个效果是一样的,觉得是一种自欺欺人的方法。有一种比较合理的改进是,先用shuffle算法(洗牌算法)对原始数组进行处理,使得数组中的各项值均匀地随机排列,然后再用常规快排算法。
// quicksort the array
public static void sort(Comparable[] a) {
StdRandom.shuffle(a);
sort(a, 0, a.length - 1);
}
// quicksort the subarray from a[lo] to a[hi]
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int j = partition(a, lo, hi);
sort(a, lo, j-1);
sort(a, j+1, hi);
assert isSorted(a, lo, hi);
}
还有一种改进旋转点的方法是采用三者取中划分。就是从数组选出三个元素,使用这三个元素的中间值的那个元素作为划分点。
二、三路划分快速排序算法。改算法将数组划分为3部分,比划分元素小的元素,与划分元素相等的元素,比划分元素大的元素。然后递归处理比划分元素小的元素和比划分元素大的元素。
// quicksort the subarray a[lo .. hi] using 3-way partitioning
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int lt = lo, gt = hi;
Comparable v = a[lo];
int i = lo;
while (i <= gt) {
int cmp = a[i].compareTo(v);
if (cmp < 0) exch(a, lt++, i++);
else if (cmp > 0) exch(a, i, gt--);
else i++;
}
// a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi].
sort(a, lo, lt-1);
sort(a, gt+1, hi);
assert isSorted(a, lo, hi);
}
三、快排的综合改进版。当递归调用快速排序算法时,处理的数组的规模会越来越小,当小的一定程度时,采用比较简单的插入排序算法会比快速排序更有优势;同时采用前面讲到的三者取中划分方法和三路快排。
private static void sort(Comparable[] a, int lo, int hi) {
int N = hi - lo + 1;
// cutoff to insertion sort
if (N <= CUTOFF) {
insertionSort(a, lo, hi);
return;
}
// use median-of-3 as partitioning element
else if (N <= 40) {
int m = median3(a, lo, lo + N/2, hi);
exch(a, m, lo);
}
// use Tukey ninther as partitioning element
else {
int eps = N/8;
int mid = lo + N/2;
int m1 = median3(a, lo, lo + eps, lo + eps + eps);
int m2 = median3(a, mid - eps, mid, mid + eps);
int m3 = median3(a, hi - eps - eps, hi - eps, hi);
int ninther = median3(a, m1, m2, m3);
exch(a, ninther, lo);
}
// Bentley-McIlroy 3-way partitioning
int i = lo, j = hi+1;
int p = lo, q = hi+1;
while (true) {
Comparable v = a[lo];
while (less(a[++i], v))
if (i == hi) break;
while (less(v, a[--j]))
if (j == lo) break;
if (i >= j) break;
exch(a, i, j);
if (eq(a[i], v)) exch(a, ++p, i);
if (eq(a[j], v)) exch(a, --q, j);
}
exch(a, lo, j);
i = j + 1;
j = j - 1;
for (int k = lo+1; k <= p; k++) exch(a, k, j--);
for (int k = hi ; k >= q; k--) exch(a, k, i++);
sort(a, lo, j);
sort(a, i, hi);
}
// sort from a[lo] to a[hi] using insertion sort
private static void insertionSort(Comparable[] a, int lo, int hi) {
for (int i = lo; i <= hi; i++)
for (int j = i; j > lo && less(a[j], a[j-1]); j--)
exch(a, j, j-1);
}
// return the index of the median element among a[i], a[j], and a[k]
private static int median3(Comparable[] a, int i, int j, int k) {
return (less(a[i], a[j]) ?
(less(a[j], a[k]) ? j : less(a[i], a[k]) ? k : i) :
(less(a[k], a[j]) ? j : less(a[k], a[i]) ? k : i));
}