快排在选取主元的时候,每次都选取最右边的元素。当序列为有序时,会发现划分出来的两个子序列一个里面没有元素,而另一个则只比原来少一个元素。为了避免这种情况,引入一个随机化量来破坏这种有序状态。
在随机化的快排里面,选取a[left…right]中的随机一个元素作为主元,然后再进行划分,就可以得到一个平衡的划分。
#include<bits/stdc++.h>
#define random(x) rand()%(x)
using namespace std;
void swap(int *a,int n,int m)
{
int t;
t=a[n];
a[n]=a[m];
a[m]=t;
}
int * partition(int *a,int L,int R)
{
int less=L-1;
int more=R;
while(L<more)
{
if(a[L]<a[R])
swap(a,++less,L++);
else if(a[L]>a[R])
swap(a,L,--more);
else if(a[L]==a[R])
{
L++;
}
}
swap(a,more,R);
int p[2]={less+1,more};
return p;
}
void quicksort(int *a,int L,int R)
{
if(L<R)
{
swap(a,random(R-L+1),R);//与经典快排不同,随机在数组中选择一个数字进行比较
int *p=partition(a,L,R);
quicksort(a,L,p[0]-1);
quicksort(a,p[1]+1,R);
}
}
int main()
{
int n;
cin>>n;
int a[n];
for(int i=0;i<n;i++)
{
cin>>a[i];
}
quicksort(a,0,n);
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
cout<<endl;
return 0;
}
多次实验结果:
10w:
算法 | 第1次耗时 | 第2次耗时 | 第3次耗时 | 平均耗时 |
---|---|---|---|---|
普通快排 | 13ms | 15ms | 15ms | 14.333ms |
随机化版本快排 | 25ms | 25ms | 27ms | 25.667ms |
100w:
算法 | 第1次耗时 | 第2次耗时 | 第3次耗时 | 平均耗时 |
---|---|---|---|---|
普通快排 | 101ms | 103ms | 96ms | 100ms |
随机化版本快排 | 119ms | 101ms | 105ms | 108.333ms |
1000w:
算法 | 第1次耗时 | 第2次耗时 | 第3次耗时 | 平均耗时 |
---|---|---|---|---|
普通快排 | 1397ms | 1379ms | 1338ms | 1371.333ms |
随机化版本快排 | 1241ms | 1187ms | 1258ms | 1228.667ms |
随机化快排因为要生成随机数,所以有一些性能损失,所以数据规模较小,数据分布均匀时普通快排还是比随机化快排要快些的,不过随着数据规模的上升,随机化快排的性能优势就展现出来了。
有序序列
下来才是展示快排才华的时候,假设当输入数组已经是排好序的,这两个算法的性能差距又有多少?
之前的数组生成代码不变,只是在调用两个算法之前,先调用一下快排将数组排序,然后将两个有序的数组作为参数传进去。
10w:
10w的普通快排……已经栈溢出了。
算法 | 第1次耗时 | 第2次耗时 | 第3次耗时 | 平均耗时 |
---|---|---|---|---|
普通快排 | 溢出 | 溢出 | 溢出 | 溢出 |
随机化版本快排 | 15ms | 7ms | 6ms | 9.333ms |
1w:
试一试1w的
算法 | 第1次耗时 | 第2次耗时 | 第3次耗时 | 平均耗时 |
---|---|---|---|---|
普通快排 | 98ms | 94ms | 92ms | 94.667ms |
随机化版本快排 | 2ms | 1ms | 0ms | 1ms |
1000w:
看下1000w下随机化快排是否有影响
算法 | 第1次耗时 | 第2次耗时 | 第3次耗时 | 平均耗时 |
---|---|---|---|---|
随机化版本快排 | 696ms | 733ms | 689ms | 706ms |
参考书籍:机械工业出版社 第三版*《算法导论》*部分内容引自原书