快速排序
public class Quick {
private Quick(){}
private static boolean less(Comparable v,Comparable w){
return v.compareTo(w)<0;
}
private static void exch(Comparable[] a,int vIndex,int wIndex){
Comparable temp = a[vIndex];
a[vIndex] = a[wIndex];
a[wIndex] = temp;
}
// 分区
private static int partition(Comparable[] a,int lo,int hi){
int i = lo;
int 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;
exch(a, i, j);// 将找到的两个值交换
}
exch(a,lo,j);// 放回中间那么,左边都是小于该值的,右边都是大于该值的
return j;// 中间值
}
private static void sort(Comparable[] a,int lo,int hi){
if(lo>=hi)return;
int mid = partition(a,lo,hi);
sort(a,lo,mid-1);// 排序左边
sort(a,mid+1,hi);// 排序右边
}
public static void sort(Comparable[] a){
StdRandom.shuffle(a);// 打乱数组
sort(a,0,a.length-1);
}
}
实现策略
对数组进行切分,从左到右找到第一个大于切分值的元素,从右往左找到第一个小于切分值的元素,进行交换,lo与hi指针相遇时退出循环交换切分值和相遇指针元素,如此切分值左边都是小于切分值的元素,切分值右边都是大于切分值的元素。
分析
快速排序与归并排序互补,都是分治思想的体现,归并排序先进行排序后进行归并,快速排序先进行切分后进行排序。快速排序的最好情况是每次切分过后切分值都刚好落在中间,最坏情况是每次切分后有一边数组总是为空(切分每次刚好是最值),那么交换次数为1+2+…N-1+N~N²/2。尽管快速排序最差情况下为平方级别,比归并排序差,但是大量的工作证明快速排序运行时间在最差情况下的几率较小,并且我们仍然可以进行一些优化工作,减少这种情况发生的概率。总的来说快速排序的运行时间为1.39NlgN的某个常数因子下(同归并排序,但是移动次数是小于归并排序的)。
增长数量级
最差:N2(概率较小)
最好:N(三向切分)
平均:NlgN
改进
- 小数组使用插入排序
- 对于大量重复的数组可以使用三向切分的快速排序
三向切分的快速排序
三向切分的快速排序维护三个指针,小值上限lt,索引i,大值下限gt,
// 三向切分,维护三个指针
public static void sort3Way(Comparable[] a,int lo,int hi){
int lt = lo,i=lo+1,gt=hi;
Comparable v = a[lo];
while (lt<=gt){
int cmp = a[i].compareTo(v);
if(cmp<0) exch(a,lt++,i++);// 比较值小于切分值
else if(cmp>0) exch(a,i,gt--);// i不进行自加,交换过来的值还要进行比较
else i++;// 相等的情况下索引增加
}
sort3Way(a,lo,lt-1);// 排序左边
sort3Way(a,gt+1,hi);// 排序右边
}
切分过后lo~lt-1
小于切分支 lt~gt
等于切分值 gt+1~hi
大于切分值