首先,对快速排序分析可知,在一个包含n个元素的数组上运行快速排序时,记总共的两个元素之间的比较次数为X,则快速排序的运行时间为 O(n+X) (算法导论第三版7.4.2节引理7.1)。因此,必须了解算法在什么时候对数组中的两个元素进行比较,什么时候不进行比较。
将数组A中的各个元素重新命名为 z1,z2,z3,...,zn ,其中 zi 是数组中第i小的元素。定义 Zij=zi,zi+1,zi+2,...,zj 为 zi 和 zj 之间(含i和j)的元素集合。定义指示器随机变量:
Xij=I{zi与zj进行比较}
则算法的总共的比较次数为:
X=∑n−1i=1∑nj=i+1Xij
对上述式子两边取期望可得:
E[X]=E[∑n−1i=1∑nj=i+1Xij]=∑n−1i=1∑nj=i+1E[Xij]=∑n−1i=1∑nj=i+1Pr{zi与zj进行比较}
通常,假设每个元素的值是互异的,因此,一旦一个满足 zi<x<zj 的主元被选择后, zi 和 zj 就再也不可能被比较了,因为 zi 和 zj 已经被划分到两个不同的划分中。然而,若是 zi 在 Zij 中的其他所有元素之前被选为主元,那么 zi 将和 Zij 中除了它自身之外的所有元素进行比较。因此, zi 和 zj 当且仅当 Zij 中第一个被选为主元的元素是 zi 或者 zj 。
接着计算这一事件发生的概率。在任意一个 Zij 内的元素被选择为主元之前,整个集合均在一个划分的同一个分区之中。因此,每个 Zij 中的元素被选为主元的概率相等。因为集合中有 j−i+1 个元素,并且主元的选择是独立且随机的,所以任何元素首先被选为主元的概率为 1j−i+1 。于是:
Pr{zi于zj相比较}=Pr{zi或者zj是集合中首先被选择作为主元的元素}=2j−i+1
所以有:
E[X]=∑n−1i=1∑nj=i+12j−i+1
所以,可以有:
E[X]=∑n−1i=1∑nj=i+12j−i+1=∑n−1i=1∑n−ik=12k+1<∑n−1i=1∑n−ik=12k=∑n−1i=1O(lgn)=O(nlgn)
由此可得,在输入元素均不相同的情况下,随机化的快速排序算法的期望运行时间是 O(nlgn) 。
对于上式,又有:
E[X]=∑n−1i=1∑nj=i+12j−i+1=∑n−1i=1∑n−ik=12k+1≥∑n−1i=1∑n−ik=122k≥∑n−1i=1Ω(lgn)=Ω(nlgn)
所以,又有,随机化的快速排序算法的期望运行时间为 Ω(nlgn) 。由上可知,随机化的快速排序算法的期望运行时间为 Θ(nlgn) 。
当输入数据已经几乎有序时,插入排序的速度很快。在实际应用中,可以利用这一特点来提高快速排序的速度。当对一个长度小于k的子数组调用快速排序时,可以让它不做任何处理就返回。当上层快速排序调用返回后,对整个数组进行插入排序来完成排序过程。该算法的期望时间复杂度为 O(nk+nlg(n/k)) 。该算法的时间复杂度分析如下:
对于快速排序结束后的插入排序,假设每一小结的插入排序时间复杂度为O(k^2),则n/k个小结的时间复杂度为 nkO(k2)=O(nk) 。
对于随机的快速排序过程,时间复杂度是 O(nlg(n/k)) ,网上很多解答说这个利用指示器随机变量,然后计算j与i的距离大于k的均值即可,但是笔者认为,在划分过程中,j于i之间的距离小于k时的比较也可能出现,所以不可以单单这样计算,然而想不出正确解法,故留待之后解决。
C语言实现优化的快速排序如下:
void insertion_sort(int *source, int head, int tail) {
if (head > tail) {
fprintf(stderr, "Error head and tail\n");
return;
}
int i = head + 1;
int j = head;
int temp;
for (i = head + 1; i <= tail; i++) {
temp = source[i];
for (j = i - 1; j >= head && source[j] > temp; j--) {
source[j + 1] = source[j];
}
source[j + 1] = temp;
}
}
int randomized_partition(int *source, int head, int tail) {
swap(source + rand() % (tail - head + 1) + head, source + tail);
int x = source[tail];
int i = head - 1, j = head;
for (j = head; j < tail; j++) {
if (source[j] <= x) {
i++;
swap(source + j, source + i);
}
}
swap(source + tail, source + i + 1);
return i + 1;
}
void limited_quick_sort(int *source, int head, int tail, int k) {
if (tail - head >= k)
return;
int mid = randomized_partition(source, head, tail);
limited_quick_sort(source, head, mid - 1, k);
limited_quick_sort(source, mid + 1, tail, k);
}
void insertion_optimized_quick_sort(int *source, int head, int tail, int k) {
limited_quick_sort(source, head, tail, k);
insertion_sort(source, head, tail);
}