快速排序的基本思路是,在给定数组中选定一个数 v v v 并将数组中元素重新排列使得 v v v 左边的元素小于等于它右边的元素大于等于它,然后继续对左右两边的子序列进行排序。
对数组 S S S:
- S S S中元素个数为0 or 1 ,return
- 取 ∀ v ∈ S \forall v\in S ∀v∈S
- 将
S
S
S中余下元素分为两个不相交集合:
S 1 = { x ∈ S − { v } ∣ x ≤ v } S_1=\{x\in S-\{v\} |x\leq v\} S1={x∈S−{v}∣x≤v} 和 S 2 = { x ∈ S − { v } ∣ x ≥ v } S_2=\{x\in S-\{v\} |x\ge v\} S2={x∈S−{v}∣x≥v} - 分别对 S 1 S_1 S1和 S 2 S_2 S2进行快排
由此可看出优化的关键在于 2取点 和 3分割 这两步的实现
取点
取第一个元素
输入是预排序或反序时会产生很糟糕的分割
取随机元素(randomized quicksort)
随机数的生成有一定的开销
取数组左端、右端、中间位置上三个数的中位数(median-of-three method)
能够避免预排序输入时产生坏情况
分割
常规做法
(数据结构书上就用的这个
- (若 v v v 不是第一个元素) 将 v v v 与第一个元素交换
- i , j i,j i,j 分别从待分割数据段左右向中间移动,具体看代码吧,代码比文字描述更直观
while(i<j) {
while(i<j && arr[j]>=v) --j;
if(i<j) S[i++]=arr[j];
while(i<j && arr[i]<= v) ++i;
if(i<j) arr[j--]=arr[i];
}
- 递归
单侧递归
以左侧为例,分割后将左侧子序列递归,将 l l l 移至分割点右侧继续对右侧的分割
(以下代码现写的,没执行过,会意就行XD)
void qsort_2(int *arr, int l, int r){
while(l<r){
int i=l,j=r,v,m=(l+r)/2; //取点采用三数中值分割
v = arr[l]+arr[r]+arr[m]-max(arr[l],arr[r],arr[m])-min(arr[l],arr[r],arr[m]);
if(arr[r]==v) {arr[r]=arr[l]; arr[l]=v;}
if(arr[m]==v) {arr[m]=arr[l]; arr[l]=v;}
while(i<j){
while(i<j && arr[j]>=v) --j;
if(i<j) S[i++]=arr[j];
while(i<j && arr[i]<= v) ++i;
if(i<j) arr[j--]=arr[i];
}
arr[i] = v;
qsort_2(arr,l,i-1);//左子序列进入递归
l = i+1; //l移到当前分割点右边,继续对右子序列进行分割
}
return ;
}
不知道叫啥
同样是 i , j i,j i,j 分别从两端向中间移动, i , j i,j i,j 都停止时交换 i , j i,j i,j 所指位置的值
(算法书上一般核心思想就是这个,这里还结合了前文提到的单侧递归来实现
(同上会意就行
void qsort_3(int *arr, int l, int r){
while(l<r){
int i=l,j=r,v,m=(l+r)/2; //取点采用三数中值分割
v = arr[l]+arr[r]+arr[m]-max(arr[l],arr[r],arr[m])-min(arr[l],arr[r],arr[m]);
if(arr[r]==v) swap(arr[r],arr[l]);
if(arr[m]==v) swap(arr[m],arr[l]);
do{
while(arr[i]<v) ++i;
while(arr[j]>v) --j;
if(i<=j){
swap(arr[i],arr[j]);
++x;
--y;
}
}while(i<=j);
qsort_3(arr,l,j);//左子序列进入递归
l = v; //l移到当前分割点右边,继续对右子序列进行分割
}
return ;
}
小数组优化
子数组足够小( N ≤ 15 N\leq15 N≤15)时改用插入排序
或不进行排序,在快排结束后进行一次插入排序
.
.
.
//做完这次整理的收获是,原来课本和我自己入的几本书之间水平是真的有差距的啊(课本太菜了(连课本都没读透的我更菜,对不起