本节的主题是快速排序,它可能是运用最为广泛的排序了,因为它很快.相比于其它算法而言,它适用于各种不同的输入数据且在一般应用中比其它算法快得多,快速排序引人注目的是它只需要一个小小的辅助栈,且将长度为N的数组排序所需要的时间和NlogN成正比,我们已经学习过的算法都无法同时结合这两个优点.
该算法的关键部分在于切分,这个过程满足以下三个条件:
-
对于某个j,A[j]已经排定
-
A[lo]到到A[j-1]所有元素都不大于A[j]
-
A[j+1]到A[hi]所有元素都不小于A[j]
我们就是通过不断的地切分来进行快速排序的.
简单快速排序
public class Quick {
public static void main(String[] args) {
int A[] = { 12, 234, 4, 3, 123, 5, 32, 64, 13, 1, 3, 13, 12, 89};
sort(A, 0, A.length - 1);
show(A);
}
public static void show(int[] A) {
for(int i = 0; i < A.length; i++)
System.out.print(A[i]+" ");
}
public static void sort(int[] 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);
}
public static int partition(int[] A, int lo, int hi) {
//将分组切割为A[lo..i-1],A[i],A[i+1..hi]
int i = lo, j = hi + 1;
int v = A[lo];
while (true) {
//扫描左右,检查扫描是否结束并且交换元素
while (A[++i] > v)
if (i == hi)
break;
while (A[--j] < v)
if (j == lo)
break;
if (i >= j)
break;
exch(A, i, j);
}
exch(A, lo, j);
return j;
}
public static void exch(int[] A, int i, int j) {
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
和大多数排序算法一样,改进快速排序性能的一个简单方法基于以下两点:
-
对于多数小数组,快速排序比插入排序慢
-
因为递归,快速排序的sort()方法在小数组中也会调用自己,因此简单地将排序小数组时的代码改动一小步就可以实现这一点:
将if\space(hi<=lo)return;改进成if\space(hi<=lo+M)Insertin.sort(A,lo,hi);return ;转换参数M是和系统相关的,但是5到15之间的值大部分情况下都能令人满意.
三取样切分快速排序
改进快速排序性能的第二个方法是使用子数组中的一小部分元素的中位数来切分子数组,但是代价是需要计算中位数.人们发现将取样大小设置为3并且用大小居中的元素切分的效果最好.
public class Quick3way { public static void main(String[] args) { int[] A = { 31, 34, 2, 5, 3465, 22, 46, 5, 8, 76, 123, 3, 41, 4, 25, 5, 54 }; sort(A, 0, A.length - 1); show(A); } public static void show(int A[]) { for (int i = 0; i < A.length; i++) { System.out.print(A[i]+" "); } } public static void sort(int[] A, int lo, int hi) { if (hi <= lo) return; int lt = lo, i = lo + 1, gt = hi; int v = A[lo]; while (i <= gt) { int cmp = A[i] > v ? 1 : 0; if (cmp == 1) exch(A, i, gt--); else if (cmp == 0) exch(A, lt++, i++); else i++; } sort(A, lo, lt - 1); sort(A, gt + 1, hi); } public static void exch(int[] A, int i, int j) { int temp = A[i]; A[i] = A[j]; A[j] = temp; } }
这段代码的切分能够将和切分元素相等的元素归位,这样它们就不会被包含在递归调用处理的子数组中.对于存在大量重复元素的数组,这样的方法能够大大加快速度.