title: Quicksort
date: 2023-10-2 18:18:49
tags: Computer science
快速排序和随机化快速排序
快速排序
描述
首先,我们介绍快速排序。快速排序与归并排序类似,基于分治的思想,将问题分解为子问题解决,再合并,但由于快速排序是不需要开辟临时变量的原址操作,因此不需要合并,详见以下说法。
分解
我们对数组array[left,…,right]进行操作,将其划分为三个部分:array[left,p-1],array[p+1,right],array[p]。所有的array[left,p-1]中所有的元素小于(大于)array[p],所有的array[p+1,right]的所有元素大于(小于)array[p]。
解决
通过递归调用快速排序,对子数组array[left,p-1]和array[p+1,right]进行排序。
此时array[left,right]已经有序。
代码
书上已经给出伪代码,下面给出java代码实现:
public static int Partition(int[] a,int left,int right){
int x = array[right];
int p = left - 1;
if(array[i] <= x){
int x = array[right];
int p = left - 1;
for(int i = left;i < right;i++){
p++;
swap(array,array[p],array[i]);
}
}
swap(array,array[p+1],array[right])
return p+1;
}
此部分代码对array[left,right]代码实现了原址重排。
下面给出图解:
忽略拙劣的画图技术,我们可以看到上面数列被分为了四个部分,表示比主元x小的部分,比x大的部分,未处理的部分,主元x,在图中r所指向的值就是主元x:
l | <=x | p | >x | i | 未处理 | x® |
---|
Partition维护了四个部分:在array[l,p]上的元素都不大于x,array[p+1,i]上的元素都大于x,array[i,r]上的元素未经处理。
那么对已经划分的数组的两部分分别进行递归调用:
public static void QuickSort(int[] array,int left,int right){
if(left < right){
int p = Partition(a,left,right);
QuickSort(array,left,p-1);
QuickSort(array,p+1,right);
}
}
性能分析
Partition的时间复杂度为O(n),其中n = r - l + 1;QuickSort的时间复杂度取决于Partition的划分情况;
最坏情况
当划分产生的子问题的元素分别是n-1和0时,划分的最坏情况产生了。
假如每次的划分都是这种极不平衡的划分情况,已知Partition的的时间复杂度是O(n),当划分产生一个空元素的集合,调用直接返回,T(0) = O(1),其递归式可以表示为:
T(n) = T(n-1)+T(0)+O(n) = T(n-1)+O(n),
那么每一层的递归代价的累加和就是一个算术级数,即O(n^2).
最佳情况
当划分产生的子问题的元素都不大于n/2,划分的最佳情况诞生。
可以得到递归式为:
T(n) = 2T(n/2)+O(n).
由主定理(Master Theorem)得时间复杂度为O(nlgn);
平衡情况
平衡情况的划分的时间复杂度可以观察递归树得到:
比如某个问题的解决的递归式满足:
T(n) = T(9n/10)+T(n/10)+O(n)
递归树的节点是问题的规模,假如用常数c,令cn成为每一次递归的代价,每一次递归的代价至多为cn,当递归的深度为
l
o
g
10
/
9
n
=
O
(
l
g
n
)
log_{10/9}n = O(lgn)
log10/9n=O(lgn)
,达到终止,因此总时间复杂度为O(nlgn).
可见,当一个划分产生一个0元素集合时,时间复杂度退化到O(n^2),为了避免这点,我们可以有了快速排序的随机化版本。
随机化快速排序
为了获得期望的划分情况,我们在选取划分的主元时可以随机选取array[left,right]的随机元素作为主元,我们仅需要对Partition进行修改:
public static int RandomPartition{
r = random.nextint(right-left)+left;
swap(array[right],array[r]);
return Partition(a,left,right);
}
其余部分相同。