引用严蔚敏的《数据结构》的话,在所有数量级都为nlgn的排序方法中,快排的常数因子k最小。这意味着快排是目前被认为是最好的一种内部排序方法。
下面先放出快排的代码:
void QuickSort(int *a,int low,int high){
if(low<high){
int l = low,h = high,flag = a[low];
while(l<h){
while(l<h&&a[h]>=flag) h--;
a[l] = a[h];
while(l<h&&a[l]<=flag) l++;
a[h] = a[l];
}
a[l] = flag;
QuickSort(a,low,l-1);
QuickSort(a,l+1,high);
}
}
所以分析快排的时间性能就很有必要了。影响快排的重要因素是枢轴数的选择。理想情况下,如果每次划分两个子数组的长度都相同,那么效率肯定会是最高的,那如果我们每次都找到数组中的中位数作为枢轴数那就可以了。然而输入的数组本身肯定是无序的,如果要找到中位数就需要有序输入,这个做法肯定不行的,我们对快排的改进必须建立在保证递归里的一次操作时间复杂度为O(n)。
教科书上给出的方法是选择数组第一个数为枢轴数。
优化一:随机选择枢轴数
当数组部分有序,选择第一个数为枢轴数,快排的比较次数会大幅度增加,而且划分子数组的长度会差异较大,当数组基本有序,快排就蜕变为冒泡了,如果随机选择枢轴数,就可以利用随机性来规避这种情况,下面我们来修改一下代码为:
//要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a;
void QuickSort_random(int *a,int low,int high){
if(low<high){
int random = ((rand()%(high-low+1))+low);
int l = low,h = high,flag = a[random];
swap(a[random],a[low]);
while(l<h){
while(l<h&&a[h]>=flag) h--;
a[l] = a[h];
while(l<h&&a[l]<=flag) l++;
a[h] = a[l];
}
a[l] = flag;
QuickSort_random(a,low,l-1);
QuickSort_random(a,l+1,high);
}
}
改代码有一点需要注意的是,随机选取了一个数,要把这个数的位置放回第一位,然后其余代码跟原来一样就好了(其实我想不懂为什么不放回第一位,按照本来的代码就是错误的,不知道怎么修改才会不出错,希望有大神可以指导我),为了代码的正确性,不管选择哪个数为枢轴数,我都是将这个数放到第一位。
优化二:三者取中选择枢轴数
为了更容易模拟出找到整体数组的中位数,我们可以选取三个数(第一个,最后一个和中间的数),然后取三个数的中位数作为枢轴数,修改后的代码如下:
//取三者中其关键宇取中值的记录为枢轴
void quick_sort_three(int *s, int l, int r) {//取随机数为枢纽
if (l < r)
{
int x = 0;
int x1 = s[l], x2 = s[r], x3 = s[(l + r) >> 1];
if (x1 <= x2&&x2 <= x3) {
x = x2;
swap(s[r], s[l]);
}
else if (x2 <= x1&&x1 <= x3) {
x = x1;
swap(s[l], s[l]);
}
else {
x = x3;
swap(s[(l+r)/2], s[l]);
}
int i = l, j = r;
while (i < j)
{
while (i < j && s[j] >= x) // 从右向左找第一个小于x的数
j--;
if (i < j)
s[i++] = s[j];
while (i < j && s[i] <= x) // 从左向右找第一个大于等于x的数
i++;
if (i < j)
s[j--] = s[i];
}
s[i] = x;
quick_sort_three(s, l, i - 1); // 递归调用
quick_sort_three(s, i + 1, r);
}
}
这时我们来测试一下性能。
(测试环境为vs2017,release,x86,硬件为surfacepro4,i5低压+8G+256G)
输入的数组为随机生成的int整数,共有100组,长度分别为1000,10000,50000,100000
|
|
选第一个数 |
随机选一个数 |
三者取中 |
STL |
|
规模n |
|
|
|
|
|
1000 |
0.03 |
0.09 |
0.06 |
0.03 |
|
10000 |
0.71 |
1.16 |
0.91 |
0.95 |
|
50000 |
4.68 |
5.58 |
4.14 |
4.45 |
|
100000 |
8.51 |
11.06 |
8.72 |
9.47 |
神奇的是,测试结果居然是完全没有优化过的快排最快!随机选择枢轴数的方法最慢!
对于随机选择的方法,我的猜想是可能rand()这个方法消耗的时间比较多所以会变慢,然后三者取中的方法接近STL的sort方法,应该是比较好的优化方法。至于普通的快排为什么会这么快我想不明白,但是必须尊重实验结果,所以打脸的数据我还是贴出来了,欢迎各位下载我的代码测试一下或者提出意见,最后附上测试的代码的github地址,谢谢!
https://github.com/810410738/sort_test/