思想:
快速排序是一种分治的排序算法。它将一个数组分成两个子数组,将两部分独立地排序。
快速排序和归并排序是互补的:
- 归并排序将数组分成两个子数组分别排序,并将有序的子数组归并来使整个数组有序;而快速排序是 当两个子数组都有序时整个数组自然就有序了。
- 归并排序的递归调用发生在处理整个数组之前,而快速排序的递归调用发生在处理整个数组之后。
快速排序的切分:
private static int partition(Comparable[] a, int lo, int hi){
//将数组切分为a[lo...i-1], a[i], a[i+1...hi]
int i = lo; j = 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;
if(i >= j) break;
swap(a, i, j);
}
swap(a, lo, i); //将 v = a[i] 放入正确的位置
return i; //a[lo...i-1] <= a[i] <= a[i+1...hi] 达成
}
切分代码按照a[lo] 的值v 进行切分。当指针 i 和 指针 j 相遇时 主循环退出。在内循环中,a[i] 小于 v 时, 增加 i,a[j] 大于 v 时,减少 j,然后交换a[i] 和 a[j] 来保证 i 左侧的元素都小于等于 v,j 右侧的元素都大于等于 v 。当指针相遇时 交换 a[lo] 和 a[i],切分结束(这样切分值就留在 a[i] 中了)。
快排实现:
public class Quick{
public static void sort(Comparable[] a){
StdRandom.shuffle(a); //消除对输入的依赖,随机打乱
sort(a, 0, a.length - 1);
}
private static void sort(Comparable[] a, int lo, int hi){
if(lo >= hi) return; //结束递归
int i = partition(a, lo, hi) //切分
sort(a, lo, i-1); //将左半部分a[lo...i-1]排序
sort(a, i+1, hi); //将右半部份a[i+1...hi]排序
}
}
性能:
优点:快速排序切分方法的内循环会用一个递增的索引将数组元素和一个定值比较,这种简洁性是快速排序的一个优点,很难想象排序算法中还能有比这更短小的内循环了。比如,归并排序和希尔排序一般都比快速排序慢,其原因就是因为它们 还在内循环中移动数据。
缺点:在切分不平衡时,上述代码可能会极为低效。例如:如果第一次从最小的元素切分,第二次从第二小的元素切分,如此这般,每次调用只会移除一个元素,这会导致大数组需要切分很多次。这也就是上述代码:在快排前将数组随机排序的原因。
快速排序最多需要约 N2/2 次比较,但随机打乱数组能够预防这种情况。
对于大小为 N 的数组,快排的运行时间为 NlgN,归并排序也能做到这一点,但是快排一般会更快,因为移动数据的次数更少。