快速排序的核心是-paration函数。
快排的核心思想:每次选取数组中某一个元素,将小于它的所有元素放在它的左边,大于它的所有元素放在它的右边,然后递归处理左区域和右区域。
最好的情况下,复杂度是O(nlogn),最差的情况下会退化成O(n^2),但是基于概率学可以证明,复杂度的期望是接近O(nlogn)的。
优化从何谈起?
优化1:防止最差情况的发生-也就是防止选取的划分元素是该区域的min或者max,
A.基于rand()的方法,我们可以通过rand()随机生成划分元素的索引。
B.多中取值:对于一个区间,我们可以取它的L,R,(L+R)/2三个位置上的元素,然后取其中位数,这个数一定不可能是边界值-三中取值,另外还有九中取值等,但是注意,并不是越多越好,对于小区间来说,三中取值足以,但是对于九中取值,反而会浪费找中位数的常数时间,优化本末倒置。
优化2:重复元素的处理
经典的快排是一次只将一个数归位,但是对于有大量重复元素的数组来说,这样做无疑会拖慢速度。
我们需要先看一个荷兰国旗问题:
给你一串数和一个x,要求你把小于x的数放左边,等于x的数放中间,大于x的数放右边。
解题思路:基本paration函数加以变化即可,我们设置一个L=l-1表示左部区间的右端点,R=r+1表示右部区间的左端点,一个游标cur=l(初始化为当前区间的左端点)扫描当前区间。
A.如果当前元素A[cur]是小于划分元素的数,将L向右移动1位,并将该位置的元素和cur位置的元素交换,交换后cur++。(解释:我们可以知道cur>L是恒成立的,所以当A[++L]和A[cur]交换之后,A[cur]的元素值是一定满足<=划分元素条件的,无需再判断,直接后移1位)。
B.如果当前元素A[cur]是大于划分元素的数,将R向左移动1位,并将该位置的元素和cur位置的元素交换,交换后cur不动。(解释:当我们将A[--R]和A[cur]交换后,我们并不能确定当前的A[cur]和划分元素的大小关系,所以需要重新判一次)。
C.如果当前元素A[cur]是等于划分元素的数,不用交换,直接cur++。
终止条件:cur==R,这说明已经向右扫到了划分好的元素,所以没必要继续扫了。
#include<cstdio>
#include<algorithm>
using namespace std;
int n,A[100];
void paration(int l,int r,int x){
int L=l-1,R=r+1,cur=l,t;
while(cur!=R){
if(A[cur]<x){
swap(A[++L],A[cur++]);
}
else if(A[cur]>x){
swap(A[--R],A[cur]);
}
else cur++;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&A[i]);
}
paration(1,n,3);
for(int i=1;i<=n;i++){
printf("%d ",A[i]);
}
return 0;
}
改进的paration函数较朴素的paration函数有一个常数级优化,在重复元素较多的情况下可以迅速缩小递归处理的区间。上述代码的paration函数后,我们会得到一个L,R,将他们返回作为下一次的递归区间。
下面的代码采用的是随机选取划分元素以及加速paration函数的优化。
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<ctime>
using namespace std;
int n,A[100];
struct returnType{
int left,right;
};
returnType paration(int l,int r,int x){
int L=l-1,R=r+1,cur=l,t;
returnType val;
while(cur!=R){
if(A[cur]<x){
swap(A[++L],A[cur++]);
}
else if(A[cur]>x){
swap(A[--R],A[cur]);
}
else cur++;
}
val.left=L,val.right=R;
return val;
}
void quicksort(int l,int r){
if(l>r||(r-l)<=1)return;
srand((unsigned) time(NULL));
int index=rand()%n+1;
returnType val=paration(l,r,A[index]);
quicksort(l,val.left);
quicksort(val.right,r);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&A[i]);
}
quicksort(1,n);
for(int i=1;i<=n;i++){
printf("%d ",A[i]);
}
return 0;
}