原文地址:http://brianleelxt.top/2018/08/10/quickSort/
《Algorithm》(Sedgewick)笔记:快速排序
动图来源(侵删) :https://zhuanlan.zhihu.com/p/40695917
最好&平均情况 : O(nlogn) O ( n l o g n )
最坏情况 : O(n2) O ( n 2 )
最好情况 : O(logn) O ( l o g n )
最坏情况 : O(n) O ( n )
public static int partition(Comparable[] a, int lo, int hi) {
int i = lo, j = hi + 1;
Comparable v = a[lo]; //pivot
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;
}
每次切分后递归地把小于pivot的子数组和大于pivot的子数组排序。
public static void sort(Comparable[] a) {
shuffle(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);
}
代码中的shuffle(a)将数组a[]的顺序打乱,原因是当输入数组a[]有序(包括顺序和逆序)或接近有序时,会造成划分不均,此时快速排序退化成冒泡排序,时间复杂度退化为 O(n2) O ( n 2 ) ,所以可在排序前先将a[]的顺序打乱,可将时间复杂度基本维持在 O(nlogn) O ( n l o g n ) 。也可以通过在切分过程中随机选择切分元素来解决此问题。
左侧扫描时遇到大于等于(而不仅是大于)pivot的元素便停下,右侧扫描遇到小于等于(而不仅是小于)pivot的元素便停下。虽然这样会不必要地将一些等值元素交换,但可以避免时间复杂度退化为 O(n2) O ( n 2 ) 。(如当数组元素值一样时,若不等值交换,时间复杂度便会退至 O(n2) O ( n 2 ) 。
https://github.com/XutongLi/Algorithm-Learn/blob/master/src/S2_Sorting/S2_3_1_QuickSort/Quick.java
当每次都能将数组对半分时为最好情况。
此时比较次数为: CN=2CN−1+N C N = 2 C N − 1 + N ,其中 2CN−1 2 C N − 1 为两个子数组排序的成本, N N 表示用pivot和所有数组元素进行比较的成本。
等同于归并排序的时间复杂度证明,可得此递归公式的解为: CN=NlogN C N = N l o g N 。
所以最好情况下时间复杂度为 O(NlogN) O ( N l o g N ) 。
令 CN C N 为将 N N 个不同元素排序平均所需的比较次数。
易知 C0=C1=0 C 0 = C 1 = 0
对于 N>1 N > 1 ,易得归纳关系:
CN=N+1+(C0+C1+...+CN−1)/N+(CN−1+...+C0)/N C N = N + 1 + ( C 0 + C 1 + . . . + C N − 1 ) / N + ( C N − 1 + . . . + C 0 ) / N
第一项为切分的成本 (N+1) ( N + 1 ) ,第二项为将左子数组(长度可能为 0 0 到 N−1 N − 1 )排序的平均成本,第三项是将右子数组排序的平均成本。
两边都乘 N N 得到:
NCN=N(N+1)+2(C0+C1+...+CN−2+CN−1) N C N = N ( N + 1 ) + 2 ( C 0 + C 1 + . . . + C N − 2 + C N − 1 )
该等式减去 (N−1)CN−1=N(N−1)+2(C0+C1+...+CN−2) ( N − 1 ) C N − 1 = N ( N − 1 ) + 2 ( C 0 + C 1 + . . . + C N − 2 ) 得:
NCN−(N−1)CN−1=2N+2CN−1 N C N − ( N − 1 ) C N − 1 = 2 N + 2 C N − 1
两边除以 N(N+1) N ( N + 1 ) 得:
CN/(N+1)=CN−1/N+2(N+1) C N / ( N + 1 ) = C N − 1 / N + 2 ( N + 1 )
消除迭代可得:
CN∼2(N+1)(1/3+1/4+...+1/(N+1)) C N ∼ 2 ( N + 1 ) ( 1 / 3 + 1 / 4 + . . . + 1 / ( N + 1 ) )
括号内的量是曲线 2/x 2 / x 下从 3 3 到 N N 的离散近似面积加一,积分得到 CN∼2NlogN C N ∼ 2 N l o g N 。
所以平均情况下时间复杂度为 O(NlogN) O ( N l o g N ) 。
最坏情况为输入数组正序或逆序时,即每次切分两个子数组之一为空。
此时比较次数为: CN=N+(N−1)+(N−2)+...+2+1=N(N+1)2 C N = N + ( N − 1 ) + ( N − 2 ) + . . . + 2 + 1 = N ( N + 1 ) 2
所以最坏情况下时间复杂度为 O(N2) O ( N 2 ) 。
public static void sort(Comparable[] a) {
shuffle(a); //消除对输入依赖
sort(a, 0, a.length - 1);
}
public static void sort(Comparable[] a, int lo, int hi) {
if (lo >= hi)
return;
int n = hi - lo + 1;
int m = mid3Pivot(a, lo, lo + n / 2, hi);
exch(a, m ,lo);
int j = partition(a, lo, hi);
sort(a, lo, j - 1);
sort(a, j + 1, hi);
}
//返回a[i] a[j] a[k]中位数index
private static int mid3Pivot(Comparable[] a, int i, int j, int k) {
if (less(a[i], a[j])) {
if (less(a[j], a[k])) //ai
return j;
else if (less(a[i], a[k])) //ai
return k;
else //ak
return i;
}
else {
if (less(a[k], a[j])) //ak
return j;
else if (less(a[k], a[i])) //aj
return k;
else //aj
return i;
}
}
https://github.com/XutongLi/Algorithm-Learn/blob/master/src/S2_Sorting/S2_3_2_Quick3Pivot/Quick3Pivot.java
public static void sort(Comparable[] a) {
shuffle(a); //消除对输入依赖
sort(a, 0, a.length - 1);
}
public static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo)
return;
int lt = lo, i = lo + 1, gt = hi;
Comparable v = a[lo];
while (i <= gt) {
int cmp = a[i].compareTo(v);
if (cmp < 0)
exch(a, lt++, i++);
else if (cmp == 0)
i++;
else
exch(a, i, gt--);
}
sort(a, lo, lt - 1);
sort(a, gt + 1, hi);
}
给定包含 k k 个不同值的 N N 个主键,对于从 1 1 到 k k 的每个 i i ,定义 fi f i 为第 i i 个主键值出现的次数, pi p i 为 fi/N f i / N ,即为随机抽取一个数组元素时第 i i 个主键值出现的概率。
那么所有主键的香农信息量 可以定义为:
H=−(p1lgp1+p2lgp2+...+pklgpk) H = − ( p 1 l g p 1 + p 2 l g p 2 + . . . + p k l g p k )
最优 : O(NH) O ( N H ) (对于包含大量重复元素的数组,它将排序时间从线性对数级降低到了线性级别)
最差 : O(NlogN) O ( N l o g N ) (所有主键均不相同)
https://github.com/XutongLi/Algorithm-Learn/blob/master/src/S2_Sorting/S2_3_3_QuickSort3Way/Quick3Way.java