前言
在网上看到了快速排序的优化方法,但是没有完整的代码,于是自己花时间实现了代码。全部代码在尾部链接。
快速排序的目标数组分为4种:随机数组,降序数组,升序数组,重复数组。数组的长度为一百万。
随机数组:确保没有重复的元素值
降序数组:随机数组的降序排序
升序数组:随机数组的升序排序
重复数组:全部为10的数组
1、基本快速排序
基本的快速排序,就是选取开始位置或者末尾位置的元素作为基准
运行时间:
(时间单位:ms) | 随机数组 | 降序数组 | 升序数组 | 重复数组 |
基本快速排序 | 240 | 1498470 | 1518180 | 1518230 |
分析:
降序、升序、重复数组三者的时间复杂度理论上为n*n。
2、随机基准
说明:
使用随机函数,选取范围内随机的一个位置的元素值,作为基准
核心代码:
srand((unsigned)time(NULL));
int loc=rand()%(end-start)+start;
swap(nums[start],nums[loc]);
int baseValue = nums[start];
运行时间:
(时间单位:ms) | 随机数组 | 降序数组 | 升序数组 | 重复数组 |
基本快速排序 | 240 | 1498470 | 1518180 | 1518230 |
随机基准 | 980 | 650 | 790 | 1520030 |
分析:
1、使用随机基准,那么对于降序数组和升序数组,每次的划分,就可能不是n-1和1,执行时间就大幅度减小了。
2、对于随机数组,反而是增加了计算随机基准的部分,这可能是造成时间消耗上升的原因。
3、对于重复数组,不管是不是随机,划分总是n-1和1。
3、中位数为基准
说明:
在当前的一段序列中,选择第一个元素值、最后一个元素值、中间的元素值,三者中的中位数作为基准。
如序列:5,8,3,4,9,2,7,6,1。三个值为5,9,1。则选择5作为基准
核心代码:
int mid=(start+end)/2;
if(nums[mid]>nums[end])
swap(nums[mid],nums[end]);
if(nums[start]>nums[end])
swap(nums[start],nums[end]);
if(nums[start]<nums[mid])
swap(nums[start],nums[mid]);
int baseValue = nums[start];
运行时间:
(时间单位:ms) | 随机数组 | 降序数组 | 升序数组 | 重复数组 |
基本快速排序 | 240 | 1498470 | 1518180 | 1518230 |
随机基准 | 980 | 790 | 650 | 1520030 |
中位数为基准 | 250 | 110 | 70 | 1512310 |
分析:
1、对于降序数组和升序数组,取中作为基准,则能更好的将序列平分
2、对随机数组看来没有什么影响
3、对重复数组,无影响,反正值都是一样的,取不取中无所谓
4、插入+取中
说明:
1、使用中位数作为基准
2、在数列长度小于一定值时,采用插入排序
核心代码:
void quicktSort(vector<int> &nums, int start, int end)
{
if (end <= start)
return;
int divideLoc = partition(nums, start, end);
if(divideLoc>start+1)
{
if(divideLoc-start<10)
{
for (int i = start+1; i < divideLoc; i++)
for (int j = i; j >= start && (nums[j] < nums[j - 1]); j--)
swap(nums[j], nums[j - 1]);
}
else
quicktSort(nums, start, divideLoc - 1);
}
if(divideLoc<end-1)
{
if(end - divideLoc <10)
{
for (int i = divideLoc+2; i <= end; i++)
for (int j = i; j >= divideLoc + 1 && (nums[j] < nums[j - 1]); j--)
swap(nums[j], nums[j - 1]);
}
else
quicktSort(nums, divideLoc + 1, end);
}
}
运行时间:
(时间单位:ms) | 随机数组 | 降序数组 | 升序数组 | 重复数组 |
基本快速排序 | 240 | 1498470 | 1518180 | 1518230 |
随机基准 | 980 | 790 | 650 | 1520030 |
中位数为基准 | 250 | 110 | 70 | 1512310 |
取中+插入 | 250 | 90 | 60 | 1520800 |
分析:
可以看出来,对于降序和升序数组,有提升,但是并不大,甚至说可以忽略,可能是选择使用插入排序的时机不对
5、聚集+取中
说明:
将与基准相同的数字,聚集到基准周围,下次进行划分是,这些元素就不必参加划分
例如:
待排序数列: 5,8,5,3,5,4,9,2,7,5,6,1,5。
选择5作为基准,则划分后的序列为:1,2,4,3,5,5,9,5,7,5,6,8,5,划分位置的下标为4
在进行划分的时候,与基准相同的数字,要么在基准的左侧,要么在基准的右侧
将与基准相同的元素聚集到基准周围:1,2,4,3,5,5,5,5,5,9,6,8,7
核心代码:
void gather(vector<int> &nums, int &left, int &mid, int &end)
{
int loc=mid+1;
for(int i=mid+1;i<=end;i++)
{
if(nums[i]==nums[mid])
{
swap(nums[i],nums[loc]);
loc++;
}
}
left=loc;
}
运行时间:
(时间单位:ms) | 随机数组 | 降序数组 | 升序数组 | 重复数组 |
基本快速排序 | 240 | 1498470 | 1518180 | 1518230 |
随机基准 | 980 | 790 | 650 | 1520030 |
中位数为基准 | 250 | 110 | 70 | 1512310 |
取中+插入 | 250 | 90 | 60 | 1520800 |
取中+聚集 | 330 | 210 | 150 | 10 |
分析:
1、随机、降序、升序数组不包含重复元素,因此聚集步骤等于浪费时间
2、对于重复数组,则时间大大提高
6、聚集+插入+取中
运行时间:
(时间单位:ms) | 随机数组 | 降序数组 | 升序数组 | 重复数组 |
基本快速排序 | 240 | 1498470 | 1518180 | 1518230 |
随机基准 | 980 | 790 | 650 | 1520030 |
中位数为基准 | 250 | 110 | 70 | 1512310 |
取中+插入 | 250 | 90 | 60 | 1520800 |
取中+聚集 | 330 | 210 | 150 | 10 |
取中+插入+聚集 | 300 | 190 | 110 | 10 |
分析:
总体上来看,取中+插入+聚集可以达到很好的效率。
7、内置sort函数
(时间单位:ms) | 随机数组 | 降序数组 | 升序数组 | 重复数组 |
取中+插入+聚集 | 300 | 190 | 110 | 10 |
sort | 340 | 120 | 150 | 230 |
总起来看,好像比内置的sort函数还要好,感觉有点不可能,是不是自己哪里写错了。
代码可以在下面链接进行下载:
https://download.csdn.net/download/liu_feng_zi_/12844738
有错误的地方或者其他疑问,请指出,共同探讨。