快速排序比一般的排序算法都要快,它是原地排序(只需要一个很小的辅助数组),且将长度为N的数组排序所需的时间与NlgN成正比.
基本算法
快速排序也是一种分治算法,它将一个数组分成两部分分别排序,它和归并排序是互补的.
归并排序是将一个数组分成两个子数组分别排序,并将有序子数组归并以将整个数组排序;而快速排序将数组排序的方式是子数组有序时整个数组也是有序的.
在快速排序中,切分的位置取决于数组的内容.
代码实现
public static void sort(Comparable[] a){ sort(a,0,a.length-1); } public static void sort(Comparable[] a, int lo, int hi){ if(lo>=hi) return ;//递归出口 int j = partition(a,lo,hi); //切分元素 sort(a,lo,j-1); //左半部分排序 sort(a,j+1,hi); //右半部分排序 }
快速排序递归地将子数组a[lo..hi]排序,先用partition()方法将a[j]放到一个合适的位置,然后再递归将其他元素排序.
关键在于切分,这个过程使数组满足三个条件:
对于某个j,a[j]已经排定.
a[lo]到a[j-1]中所有元素都不大于a[j].
a[j+1]到a[hi]中所有元素都不小于a[j].
切分方法的实现
先随意取a[lo]作为切分元素,即那个会被排定的元素.
然后从数组的左端扫描直到找到一个大于等于它的元素,再从数组的右边扫描直到找到一个小于等于它的元素. 这两个元素显然没有排定,所以我们交换他们的位置
如此继续,我们可以保证左指针i的左侧元素都不大于切分元素,右指针j的右侧元素都不小于切分元素. 当两个指针相遇时,我们只要把切分元素a[lo]和左子数组最右边元素a[j]进行交换即可,这时切分元素就留在了a[j]中了.
轨迹图
算法实现
public static int partition(Comparable[] a, int lo, int hi){ int i = lo, j = hi + 1; //左右扫描指针 Comparable v = a[lo]; //切分元素 while(true){ //扫描到左半部分大于v的元素停止 while(less(a[++i], v)) if(i==hi) break; //扫描到右半部分小于v的元素停止 while(less(v,a[--j])) if(j==lo) break; if(i>=j) break; //交换元素 exch(a,i,j); } //交换切分元素a[lo]和左半部分最右边的元素a[i].因为a[lo]到a[j-1]中的所有元素都不大于a[j] exch(a,i,lo); return j; }
优化
1.在排序小数组时切换到插入排序
2.三取样切分(取样大小为3设中间的元素为切分元素),代价是需要计算中位数.
3.熵最优排序
三向切分的快速排序
快速排序的一种改进,使快排在有大量重复元素的数据,同样能保持高效。
我们从左到右遍历数组,维护一个指针lt使得a[lo..lt-1]中的元素全部小于v,一个指针gt使得a[gt+1..hi]中的元素全部大于v,一个指针i使得a[lt,i-1]中的元素都等于v,a[i,gt]中的元素都还未确定.
一开始令i和lo相等,遍历过程中有3种情况
a[i] < v, 将a[i]和a[lt]交换,将lt和i都加一.
a[i] > v, 将a[i]和a[gt]交换,将gt减一.
a[i] == v ,将i加一.
这些操作保证数组元素不变且缩小gt-i的值(这样循环才能终止)
public static void sort(Comparable[] a, int lo, int hi){ if(hi <= lo) return ; int lt = lo, gt = hi,i = lo + 1; Comparator v = a[lo]; while(i<=gt){ int cmp = a[i].compareTo(v); if(cmp>0) exch(a,i,gt--); else if(cmp<0) exch(a,i++,lt++); else i++; } //现在 a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]成立 sort(a,lo,lt-1); sort(a,gt+1,hi); }
算法轨迹