快速排序是由C.A.R Hoare在1960年发明的,并被选为20世纪十大算法。在不要求稳定的应用场景中,快速排序是一种性能不错的、通用的排序算法。
在阅读完Robert Sedgewick的《算法》之后,尝试把几种快速排序整理在一起并总结。快速排序的精髓在于切分位置(Partition)的选取,本文希望把重点放在几种切分的区别上,淡化快速排序的原理和概念。本文所有源码来源于[1],都是从小到大排序。
在《算法导论》中,快速排序有两种方法Hoare-Partition和Lomuto-Partition。简单的说,抛开中轴数(pivot)选取的不同不说,他们都有两个指针,Hoare的指针一头一尾,各往中间移动;Lomuto两个指针都是从前往后移动[4]。
Hoare-Partition(A, p, r)
x = A[p]
i = p - 1
j = r + 1
while true
repeat
j = j - 1
until A[j] <= x
repeat
i = i + 1
until A[i] >= x
if i < j
swap( A[i], A[j] )
else
return j
Lomuto-Partition(A, p, r)
x = A[r]
i = p - 1
for j = p to r - 1
if A[j] <= x
i = i + 1
swap( A[i], A[j] )
swap( A[i+1], A[r] )
return i + 1
QuickKR.java的实现参考了《The C Programming Language》一书,属于Lomuto方法。这是一种简单的快速排序实现,可以用在自己的小程序中,不适合用作对外发布库函数。对于某些特殊情况,它有继续优化的空间:[2,4]
//QuickKR.java
/****************************************************************************** * Compilation: javac QuickKR.java * Execution: java QuickKR N * Dependencies: StdIn.java StdOut.java * * Generate N random real numbers between 0 and 1 and quicksort them. * Uses version of quicksort from K+R. * * Reference: The C Programming Language by Brian W. Kernighan and * Dennis M. Ritchie, p. 87. * * Warning: goes quadratic if many duplicate keys. * ******************************************************************************/
public class QuickKR {
public static void sort(Comparable[] a) {
sort(a, 0, a.length - 1);
}
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
exch(a, lo, (lo + hi) / 2); // use middle element as partition
int last = lo;
for (int i = lo + 1; i <= hi; i++)
if (less(a[i], a[lo])) exch(a, ++last, i);
exch(a, lo, last);
sort(a, lo, last-1);
sort(a, last+1, hi);
}
/*************************************************************************** * Helper sorting functions. ***************************************************************************/
// is v < w ?
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
// exchange a[i] and a[j]
private static void exch(Object[] a, int i, int j) {
Object swap = a[i];
a[i] = a[j];
a[j] = swap;
}
/*************************************************************************** * Check if array is sorted - useful for debugging. ***************************************************************************/
private static boolean isSorted(Comparable[] a) {
for (int i = 1; i < a.length; i++)
if (less(a[i], a[i-1])) return false;
return true;
}
// print array to standard output
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
StdOut.println(a[i]);
}
}
/** * Reads in a sequence of strings from standard input; quicksorts them; * and prints them to standard output in ascending order. * Shuffles the array and then prints the strings again to * standard output, but this time, using the select method. */
public static void main(String[] args) {
String[] a = StdIn.readAllStrings();
QuickKR.sort(a);
show(a);
}
}
Quick.java是《算法》中的基本实现,属于Hoare方法,为什么说基本实现呢。因为后面的许多改进都是基于这个的。Quick.java(Hoare)优化了QuickKR.java(Lomuto)的局限性。
//Quick.java
/****************************************************************************** * Compilation: javac Quick.java * Execution: java Quick < input.txt * Dependencies: StdOut.java StdIn.java * Data files: http://algs4.cs.princeton.edu/23quicksort/tiny.txt * http://algs4.cs.princeton.edu/23quicksort/words3.txt * * Sorts a sequence of strings from standard input using quicksort. * * % more tiny.txt * S O R T E X A M P L E * * % java Quick < tiny.txt * A E E L M O P R S T X [ one string per line ] * * % more words3.txt * bed bug dad yes zoo ... all bad yet * * % java Quick < words3.txt * all bad bed bug dad ... yes yet zoo [ one string per line ] * * * Remark: For a type-safe version that uses static generics, see * * http://algs4.cs.princeton.edu/23quicksort/QuickPedantic.java * ******************************************************************************/
/** * The <tt>Quick</tt> class provides static methods for sorting an * array and selecting the ith smallest element in an array using quicksort. * <p> * For additional documentation, see <a href="http://algs4.cs.princeton.edu/21elementary">Section 2.1</a> of * <i>Algorithms, 4th Edition</i> by Robert Sedgewick and Kevin Wayne. * * @author Robert Sedgewick * @author Kevin Wayne */
public class Quick {
// This class should not be instantiated.
private Quick() { }
/** * Rearranges the array in ascending order, using the natural order. * @param a the array to be sorted */
public static void sort(Comparable[] a) {
StdRandom.shuffle(a);
sort(a, 0, a.length - 1);
assert isSorted(a);
}
// 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] so that a[lo..j-1] <= a[j] <= a[j+1..hi]
// and return the index j.
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 partitioning item v at a[j]
exch(a, lo, j);
// now, a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
return j;
}
/** * Rearranges the array so that a[k] contains the kth smallest key; * a[0] through a[k-1] are less than (or equal to) a[k]; and * a[k+1] through a[N-1] are greater than (or equal to) a[k]. * @param a the array * @param k find the kth smallest */
public static Comparable select(Comparable[] a, int k) {
if (k < 0 || k >= a.length) {
throw new IndexOutOfBoundsException("Selected element out of bounds");
}
StdRandom.shuffle(a);
int lo = 0, hi = a.length - 1;
while (hi > lo) {
int i = partition(a, lo, hi);
if (i > k) hi = i - 1;
else if (i < k) lo = i + 1;
else return a[i];
}
return a[lo];
}
/*************************************************************************** * Helper sorting functions. ***************************************************************************/
// is v < w ?
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
// exchange a[i] and a[j]
private static void exch(Object[] a, int i, int j) {
Object swap = a[i];
a[i] = a[j];
a[j] = swap;
}
/*************************************************************************** * Check if array is sorted - useful for debugging. ***************************************************************************/
private static boolean isSorted(Comparable[] a) {
return isSorted(a, 0, a.length - 1);
}
private static boolean isSorted(Comparable[] a, int lo, int hi) {
for (int i = lo + 1; i <= hi; i++)
if (less(a[i], a[i-1])) return false;
return true;
}
// print array to standard output
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
StdOut.println(a[i]);
}
}
/** * Reads in a sequence of strings from standard input; quicksorts them; * and prints them to standard output in ascending order. * Shuffles the array and then prints the strings again to * standard output, but this time, using the select method. */
public static void main(String[] args) {
String[] a = StdIn.readAllStrings();
Quick.sort(a);
show(a);
// shuffle
StdRandom.shuffle(a);
// display results again using select
StdOut.println();
for (int i = 0; i < a.length; i++) {
String ith = (String) Quick.select(a, i);
StdOut.println(ith);
}
}
}
if(hi<= lo + M){
Insertion.sort(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);
}
// Dijkstra 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+1;
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++;
}
sort(a, lo, lt - 1);
sort(a, gt + 1, hi);
}
// Bentley-McIlroy 3-way partitioning 上述右图
private static void sort(Comparable[] a, int lo, int hi) {
int i = lo, j = hi+1;
int p = lo, q = hi+1;
Comparable v = a[lo];
while (true) {
while (less(a[++i], v))
if (i == hi) break;
while (less(v, a[--j]))
if (j == lo) break;
// pointers cross
if (i == j && eq(a[i], v))
exch(a, ++p, i);
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);
}
i = j + 1;
for (int k = lo; k <= p; k++)
exch(a, k, j--);
for (int k = hi; k >= q; k--)
exch(a, k, i++);
}
在Java 7 runtime library中,使用的是Dual-Pivot Quicksort[7],有两个中轴数,由Vladimir Yaroslavskiy等人提出。
Bentley and McIlroy的三路切分的快速排序在Java库中工作的不错,即使输入的数据是近似有序的。M. D. MCILROY在1999年的一篇论文中提出一种方法[9],可以产生一组数据,使得快速排序的性能恶化到最差,这个方法被叫做killer adversary(快速排序的无敌对手)。64点的最差输入如下:
G. J. E. Rawlins在《Compared to what? an introduction to the analysis of algorithms》(1991年)这本书上提出了一个螺帽和螺钉的配对问题:有N个螺钉和N个螺帽,一个螺帽只能和一个螺钉配对,一个螺钉也只能和一个螺帽配对。现在只能任意去配对一组,然后去观察哪个比较大(若螺帽大,套在螺钉上就显得松;若螺钉大,就钻不进螺帽),但是不能直接比较两个螺帽或两个螺钉,有没有有效的解决方法?[10]
【Reference】
[1]《Algorithms》4th Edition http://algs4.cs.princeton.edu/23quicksort
[2]快速排序性能可视化 http://www.sorting-algorithms.com/quick-sort
[3]Tukey ninther方法 http://www.johndcook.com/blog/2009/06/23/tukey-median-ninther/
[4]Quicksort Partitioning: Hoare vs. Lomuto http://cs.stackexchange.com/questions/11458/quicksort-partitioning-hoare-vs-lomuto/11550#11550
[5]QuickSort Dijkstra 3-Way Partitioning: why the extra swapping? http://cs.stackexchange.com/questions/22389/quicksort-dijkstra-3-way-partitioning-why-the-extra-swapping
[6]fat partitioning http://pauillac.inria.fr/~maranget/X/421/09/bentley93engineering.pdf
[7]Java 7 runtime library http://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html#sort%28int%5B%5D%29
[8]台湾中央研究院,穆信成 http://www.iis.sinica.edu.tw/~scm/ncs/2010/10/dutch-national-flag-problem/
[9]A Killer Adversary for Quicksort http://www.cs.dartmouth.edu/~doug/mdmspe.pdf
[10]Matching Nuts and Bolts - Solution http://www.wisdom.weizmann.ac.il/~naor/PUZZLES/nuts_solution.html
20世纪十大算法:http://www.uta.edu/faculty/rcli/TopTen/topten.pdf
浅谈算法和数据结构: 四 快速排序http://www.cnblogs.com/yangecnu/p/Introduce-Quick-Sort.html
QuicksortIsOptimal,作者:Robert Sedgewick,Jon Bentley http://www.sorting-algorithms.com/static/QuicksortIsOptimal.pdf