快速排序
快速排序与归并排序类似,也是运用递归分治的思想,不过它不是从中间把数组a平分成2个子数组,而是选择一个切分元素a[lo]进行切分,切分过程中把比a[lo]小的元素都移到a[lo]的左边,把比a[lo]大的元素都移到a[lo]的右边,切分完成后根据a[lo]的最终位置j,把
数组分成两个子数组a1和a2,这样a1的元素都<=a[j],a2的元素都>=a[j],再用同样的切分方式把a1和a2切分,递归下去当a1和a2有序后,a就自然有序了,切分示意图如下:
代码如下
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] by returning an index j
// so that a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
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;
}
切分函数是partition(),可以结合下图理解切分的过程,待排序数组是K,R,A...C,X,O,S,初始值i = 0, j = 16,一般选择数组第1个元素v=a[lo]作为切分元素,子循环while(less(a[++i], v))递增i直到找到一个大于等于v的元素a[i],子循环while(less(v, a[--j]))递减j直到找到一个小于等于v的元素a[j],然后exch(a, i, j) i和j的元素交换位置,使i左边的元素都<=v,j右边的元素都>=v,while (true)重复这个过程直到i >= j,i和j相遇时,j左边的元素都<=v,j右边的元素都>=v,最后调用exch(a, lo, j);把v放到正确的位置j,返回j结束切分的过程。切分的过程要注意i,j别越过数组的边界 2.3.11 假如在遇到和切分元素重复的元素时我们继续扫描数组而不是停下来,证明使用这种方法的快速排序在处理只有若干种元素值的数组时运行时间是平方级别的。
快速排序最好情况是每次切法都正好能将数组对半分,比较次数时间复杂度O(NlogN),可以结合前文归并排序的依赖树来理解。
快速排序最坏的情况是每次切分后,总有一个数组为空,需要N次切法,每次切法的比较次数是N,N-1,N-2,...2,1,所以总的比较次数是N+(N-1)+(N-2)+...+2+1=(N+1)N/2,时间复杂度是O(N^2),
所以快速排序开始会先调用lStdRandom.shuffle(a);把数组随机打乱,减少对输入的依赖,这样平均情况下时间复杂度O(NlogN)
# java SortCompare Quick Merge 5000 100
For 5000 random Doubles
Quick is 1.3 times faster than Merge
For 5000 random Doubles
Quick is 0.8 times faster than Merge
运行SortCompare可以看出快速排序和希尔排序,归并排序效率差不多,一般比希尔和归并排序快,因为快速排序内循环中移动数据次数比较少。
三向切分的快速排序
快速排序有两个问题,一是切分过程中遇到与切分元素相同的元素也会停下来交换元素,二是对元素全部重复的子数组仍然会递归进行切分,所以对于有大量重复元素的数组快速排序还有很大的改进潜力,改进算法如下,对于有大量重复元素的数组它可以把时间复杂度从线性对数基本降低到线性级别。切分示意图如下:
切分过程中把数组分成三部分,小于,等于,大于切分元素的数组,这样当子数组都是重复元素时不会继续递归下去
public class Quick3way {
public static void sort(Comparable[] a) {
StdRandom.shuffle(a);
sort(a, 0, a.length - 1);
assert isSorted(a);
}
// quicksort the subarray a[lo .. hi] using 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++;
}
// a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi].
sort(a, lo, lt-1);
sort(a, gt+1, hi);
assert isSorted(a, lo, hi);
}
// ex2.3.5 给出一段代码将已知只有两种主键值的数组排序。
public class Sort2distinct {
public static void sort(Comparable[] a) {
int lt = 0;
int gt = a.length - 1;
int i = 1;
while(i <= gt) {
int cmp = a[i].compareTo(a[lt]);
if (cmp < 0) exch(a, i++, lt++);
else if(cmp > 0) {i++; lt++;}
else i++;
}
}
public static void sort2(Comparable[] a) {
int lt = 0;
int gt = a.length - 1;
int i = 1;
while(i <= gt) {
int cmp = a[i].compareTo(a[lt]);
if (cmp < 0) exch(a, i++, lt++);
else if(cmp > 0) exch(a, i, gt--);
else i++;
}
}
//ex 2.3.18 三取样切分,为快速排序实现正文所述的三取样切分(参见 2.3.3.2 节),运行双倍测试来确认这项改动的效果。
// ex2.3.18
public class Quick_ex2_3_18 {
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] by returning an index j
// so that a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
private static int partition(Comparable[] a, int lo, int hi) {
int n = hi - lo + 1;
int m = median3(a, lo, lo + n/2, hi);
exch(a, m, lo); //中位数作为切分元素
int h = less(a[lo + n/2], a[hi]) ? hi : lo + n/2;
exch(a, h, hi); // 较大的值放最右侧作为哨兵,可以去掉if(i== hi)和if(j== lo)的判断
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;
}
// return the index of the median element among a[i], a[j], and a[k]
private static int median3(Comparable[] a, int i, int j, int k) {
return (less(a[i], a[j]) ?
(less(a[j], a[k]) ? j : less(a[i], a[k]) ? k : i) :
(less(a[k], a[j]) ? j : less(a[k], a[i]) ? k : i));
}
// ex 2.3.22
public class QuickBentleyMcIlroy {
// cutoff to insertion sort, must be >= 1
private static final int INSERTION_SORT_CUTOFF = 8;
// cutoff to median-of-3 partitioning
private static final int MEDIAN_OF_3_CUTOFF = 40;
// This cla