最近一直在做排序算法的题目,插入排序,归并排序,堆排序,快速排序。
不得不说,快速排序真的很快,特别是当输入数据的个数n很大时(我都是以1亿个数据做实验,并且数据的值是随机的,相同的数据很少)。虽然快速排序,归并排序和堆排序的时间复杂度都是nlgn,但可能是因为常系数的原因,快速排序是这三种算法中最快的。
即便如此,快速排序也是有优化的空间的。针对不同的情景,有不同的优化方法。
情景1:大数据,数据值随机,并且相同数据少
在这用情况下,可以利用“当n较小时,插入排序的速度比快速排序快”这个性质,对分治法进行改进:
#define K 12
void quick_insert_sort(int *a, int p, int r)
{
int q,n;
n = r-p+1;
if(p<r){
if(n<=K){
insert_sort(&a[p],n);
return;
}
q = partition(a,p,r);
quick_insert_sort(a,p,q-1);
quick_insert_sort(a,q+1,r);
}
}
#undef K
int partition(int *a, int p, int r)
{
int x,i,j;
x = a[r];
i = p-1;
for(j=p;j<r;j++){
if(a[j]<=x){
i++;
swap(&a[i],&a[j]);
}
}
i++;
swap(&a[i],&a[r]);
return i;
}
void swap(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
void insert_sort(int *array, int size)
{
int i,j,tmp;
for(i=1;i<size;i++){
j = i-1;
tmp = array[i];
while((j>=0) && (tmp<array[j])){
array[j+1] = array[j];
j--;
}
array[++j] = tmp;
}
}
情景2:大数据,数据值随机,相同数据少,但是数据是接近有序的(即大部分已经是从大到小或者从小到大排列)
在这种情况下,可以利用随机算法,随机的选取一个pivot;为了进一步增加随机的效果,可以使用median-3算法,即每次随机的选取三个值,然后以这三个值的中间值做为pivot:
int median_three(int *a, int i1, int i2, int i3)
{
if(a[i1]>=a[i2]){
if(a[i2]>=a[i3])
return i2;
else if(a[i1]>=a[i3])
return i3;
else
return i1;
}
else{
if(a[i2]<=a[i3])
return i2;
else if(a[i1]<=a[i3])
return i3;
else
return i1;
}
}
int m3_partition(int *a, int p, int r)
{
int i1,i2,i3,i,n;
n = r-p+1;
if(n<3){ // small n, no optimization
return partition(a,p,r);
}
else{//median of 3
srand((unsigned) time(NULL));
i1 = (RAND32%n)+p;
i2 = (RAND32%n)+p;
i3 = (RAND32%n)+p;
i = median_three(a,i1,i2,i3);
swap(&a[i],&a[r]);
return partition(a,p,r);
}
}
#define K 12
void m3_quicksort(int *a, int p, int r)
{
int q,n;
n = r-p+1;
if(n>1){
if(n<=K){
insert_sort(&a[p],n);
return;
}
q = m3_partition(a,p,r);
m3_quicksort(a,p,q-1);
m3_quicksort(a,q+1,r);
}
}
#undef K
情景3:大数据,数据值随机,但是相同数据较多
在这种情况下,可以对partition算法进行改进,返回两个下标值q和t(p<=q<=t<=r),使得经过partition之后的数组满足下面三个性质:
a. A[q .. t]中所有的元素都是相等的,都等于A[q]
b. A[p .. q-1]中的每一个元素都小于A[q]
c. A[t+1 .. r]中的每一个元素都大于A[q]
这样,在partition之后,只要分别对A[p .. q-1]和 A[t+1 .. r]进行快速排序即可。这非常适用于相同数据很多的情况。
void opt_partition(int *a, int p, int r, int *q, int *t)
{
int i1,i2,i3,i,j,k,n;
n = r-p+1;
if(n<3){ // small n, no optimization
*q = *t = partition(a,p,r);
return;
}
else{//median of 3 rand
srand((unsigned) time(NULL));
i1 = (RAND32%n)+p;
i2 = (RAND32%n)+p;
i3 = (RAND32%n)+p;
i = median_three(a,i1,i2,i3);
swap(&a[i],&a[r]);
k = partition(a,p,r);
for(i=k,j=k-1;j>=p;j--){//计算q
if(a[j]==a[k]){
i--;
swap(&a[j],&a[i]);
}
}
*q = i;
for(i=k,j=k+1;j<=r;j++){//计算t
if(a[j]==a[k]){
i++;
swap(&a[j],&a[i]);
}
}
*t = i;
}
}
#define K 12
void opt_quicksort(int *a, int p, int r)
{
int q,t,n;
n = r-p+1;
if(n>1){
if(n<=K){
insert_sort(&a[p],n);
return;
}
opt_partition(a,p,r,&q,&t);
opt_quicksort(a,p,q-1);
opt_quicksort(a,t+1,r);
}
}
#undef K
需要指出的是,并不是把所有的优化都加上效果就一定最好。因为增加的优化代码也是要消耗时间的,如果优化代码所带来的时间上的减少还不能抵消其自身对时间的增加,那么也是没有意义的。