目录
前言
在前几章学习了快速排序不同版本的实现,他们的时间效率都是差不多一样的
数据结构 --------- 快速排序算法的实现(前后指针法版本)-CSDN博客
数据结构 --------- 快速排序算法的实现(挖坑法版本)-CSDN博客
数据结构 --------- 快速排序算法的实现(hoare版本)-CSDN博客
接下来要学习的是快速排序的时间复杂度
以及如何规避快速排序中最坏情况(也就是会导致时间效率最低情况)的方法
快速排序的时间复杂度
通过递归分治的思想,每次把 n 都一分为二,分到最后都为 1 时,排序也就完成了
得出快速排序的时间复杂度为(大O渐进表示法):N * longN
快速排序时间效率最坏的情况
当数组有序或者接近有序时,快速排序的时间效率最低
低到时间复杂度为:O(N^2) 的程度
因为每次选择的 key 是最左边的值,那么就拿升序来说
因为升序,所以 key 每次都是最小的值,那么此区间的左边几乎不存在,也就是又要在 n-1 个值中选出 key ,同样是递归分治的思想,但是因为每次排序只能让 key 停留在最终位置
所以要排完整个数组,那么就是一个等差数列,时间复杂度也就是:O(N^2)
规避快速排序最坏情况的方法
三数取中法
方法思维:
选取当前区间中左中右三个值,把它们比较出来,选中间值作为 key
但是不能改变快速排序的整体逻辑,所以选出中间值后,和最左边的值交换,这样既没有改变快速排序本身的逻辑,也规避了数组是有序或者接近有序的情况
方法代码:
int GetMidIndex(int* a, int left, int right)
{
int midi = (left + right) / 2;
// 找出中间值
if (a[midi] < a[left])
{
if (a[midi] > a[right]) //[right midi left]
{
return midi;
}
else if (a[right] > a[left]) //[midi left right]
{
return left;
}
else //[midi right left]
{
return right;
}
}
else
{
if (a[midi] < a[right]) //[left midi right]
{
return midi;
}
else if (a[right] < a[left]) //[right left midi]
{
return left;
}
else //[left right midi]
{
return right;
}
}
}
注意:是要返回中间值的下标,才能方便和最左边的值交换
将三数取中放在快速排序算法中(以前后指针版本举例)
代码演示:
int PartSort_Pointer(int* a, int left, int right)
{
// 三数取中
int midi = GetMidIndex(a, left, right);
Swap(&a[midi], &a[left]);
int prev = left;
int cur = prev + 1;
int keyi = left;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
// 把 key 放在最终位置
Swap(&a[prev], &a[keyi]);
return prev;
}
void QuickSort(int* a, int begin, int end)
{
// 前后指针法
if (begin >= end)
{
return;
}
int key = PartSort_Pointer(a, begin, end);
// [begin,key-1] key [key+1,end]
QuickSort(a, begin, key - 1);
QuickSort(a, key + 1, end);
}
得到中间值的下标后,直接和最左边的值进行交换,然后其他的逻辑都不变
代码验证: