快速排序
最差情况:元素基本有序
类似单支树的结构
总比较次数2+3+4+…+N
时间复杂度为O(N^2)
最优情况:每次取的基准值都刚好将区间元素进行划分
二叉平衡树
高度logN
快排时间复杂度O(NlogN)
所以快速排序比较适合元素杂乱的场景
#include<stdio.h>
void Swap(int* left, int* right)
{
int temp = *right;
*right = *left;
*left = temp;
}
//快排的基准值:三数取中法 降低取到极值问题
int GetMiddleIndex(int array[], int left, int right)
{
int mid = left + ((right - left) >> 2);
if (array[left] < array[right - 1])
{
if (array[mid] < array[left])
return left;
else if (array[mid] > array[right - 1])
return right - 1;
else
return mid;
}
else
{
if (array[mid] > array[left])
return left;
else if (array[mid] < array[right - 1])
return right - 1;
else
return mid;
}
}
//划分方式一 O(N)
int Partion1(int array[], int left, int right)//默认初始基准值为最右侧
{
int keyIndex = GetMiddleIndex(array, left, right);
Swap(&array[keyIndex], &array[right - 1]);
int begin = left;
int end = right - 1;
int key = array[end];
while (begin < end)
{
while (begin < end && array[begin] <= key)
begin++;
while (begin < end && array[end] >= key)
end--;
if (begin < end)
{
Swap(&array[begin], &array[end]);
}
}
if (begin != right - 1)
{
//Swap(&array[begin], &key);错误
Swap(&array[begin], &array[right - 1]);
}
return begin;
}
//划分方式二:挖坑法 O(N)
int Partion2(int array[], int left, int right)//默认初始基准值为最右侧
{
int begin = left;
int end = right - 1;
int key = array[end];//end现在是一个坑
while (begin < end)
{
//begin从前往后找,找比基准值大的元素
while (begin < end && array[begin] <= key)
begin++;
//找到比基准值大的元素
if (begin < end)
{
array[end] = array[begin];
end--;
}
//begin位置成为一个坑位
//让end从后往前找比基准值小的元素,填充begin位置
while (begin < end && array[end] >= key)
end--;
//找到比基准值小的元素
if (begin < end)
{
//填充begin位置
array[begin] = array[end];
begin++;
}
//end为新坑位
}
//最后用key基准值将最后一个坑位填充
array[begin] = key;
return begin;
}
//划分方式三:前后指针 O(N)
//当prev和cur是一前一后的关系时,说明cur从前往后找的过程中暂时未遇到比基准值大的元素
//当prev和cur中间有间隔时,prev的下一个位置到cur之间的元素都是比基准值大的元素
int Partion3(int array[], int left, int right)//默认初始基准值为最右侧
{
int cur = left;
int prev = cur - 1;
int key = array[right - 1];
while (cur < right)
{
//让cur从前往后找,找比基准值小的元素
//找到之后,如果prev的下一个位置和cur不相等则交换
if (array[cur] < key && ++prev != cur)
{
Swap(&array[prev], &array[cur]);
}
++cur;
}
if (++prev != right - 1)
{
Swap(&array[prev], &array[right - 1]);
}
return prev;
}
void QuickSort(int array[], int left, int right)
{
if (right - left <= 1)
return;
//在[left,right]区间找基准值,将区间分割成两部分
int div = Partion1(array, left, right);
//div为分割后基准值在数组的下标
//递归基准值的左侧
QuickSort(array, left, div);
//递归基准值的右侧
QuickSort(array, div + 1, right);
}
int main()
{
int array[] = { 3,1,8,6,0,2,7,9,4,5 };
//快速排序
QuickSort(array, 0, sizeof(array) / sizeof(array[0]));
for (int i = 0; i < 10; i++)
{
printf("%d ", array[i]);
}
return 0;
}
上机实践:
进一步提高排序效率
随着递归不断进行,区间中元素不断减少,少到一定规模,则不需要往下划分了,直接进行插入排序。
因为:当数量少时,适合插入排序——而且如果不断往下划分也会增加递归深度,如果元素过多,递归深度可能会导致栈溢出,则可以设置一个阈值,当元素少于阈值时,采用插入排序。
如果数据量比较大,在没有达到阈值之前,都通过快速递归来划分区分的,通过递归分化时,数据量过大导致递归深度过深栈溢出:
1.可以设置快排递归的阈值,当递归的深度到达阈值,则不递归(阈值使用logN来计算)
2.为了不影响快排的时间复杂度:后序的分组直接采用堆排序来处理
采用堆排序优化原因是:堆排序时间复杂度也是O(NlogN)