目录
引入
在此篇文章中已经介绍了快速排序的多种实现方式:快速排序的多种实现方式-CSDN博客
毋庸置疑的是快速排序是一种非常优秀的排序方式,因此被广泛的使用也凸显出快速排序的重要地位。但是快速排序的普通实现方式在面对一些特殊场景使就会显得非常笨重,比如待排序的数组为有序的情况下,每次划分只得到一个比上一次少一个记录的子序列,时间复杂度就变为:O( N* N )
小优化------三数取中
" 三数取中 " 规则:
比较当前数组中第一个记录、最后一个记录和中间一个记录的关键字,取三个关键字中大小为中间的记录作为枢轴记录,并将其与第一个记录的位置进行交换
图解:
代码实现:
//快速排序------双指针
void QuickSort(int* arr, int begin, int end)
{
if (begin >= end)
{
return;
}
//三数取中
int pos = begin;
int mid = (begin + end) / 2;
if (arr[pos] < arr[mid])
{
pos = mid;
}
if (arr[pos] > arr[end])
{
pos = end;
}
swap(&arr[begin], &arr[pos]);
//基准位置
int key = pos;
//始终指向比基准小的位置,后续与 cur 交换
int prev = begin;
//探路,找比基准值小的数
int cur = prev + 1;
while (cur <= end)
{
//找到比基准值小的数,与 prev 先加一在交换
if (arr[cur] <= arr[key])
{
//慢指针先加一
prev++;
swap(&arr[cur], &arr[prev]);
}
cur++;
}
//基准与prev交换
swap(&arr[key], &arr[prev]);
//二分
QuickSort(arr, begin, prev - 1);
QuickSort(arr, prev + 1, end);
}
三数取中理论上可以说在平均情况下,快速排序的时间复杂度为O( NlogN ),但是在面对有重复大量的数组时,快速排序的时间复杂度还是O( N*N ),比如:
三路划分
当面对数组中有大量重复元素时,三路划分核心思想有点类似于【博客快速排序的多种实现方式-CSDN博客】当中讲的【hoare法】和【快慢指针法】的结合。其主要思想就是将数组中的数分为三个部分【比基准小的值】【与基准相等的值】【比基准大的值】,因此被称为三路划分算法
算法思路:
- key 默认为 left 位置
- left 指向数组的最左边,right 指向数组的最右边,cur 指向 left + 1 的位置
- cur 遇到比 key 小的值后跟 left 位置进行交换,随后 left++,cur++
- cur 遇到比 key 大的值后跟 right 位置进行交换,随后 right--
- cur 遇到与 key 相等的值时 cur++
- 直到 cur > right 结束
图解:
代码实现:
//交换两个数的值
void swap(int* num1, int* num2)
{
int tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
//快速排序------三路划分
void QuickSort(int* arr, int begin, int end)
{
if (begin >= end)
{
return;
}
//三数取中
int pos = begin;
int mid = (begin + end) / 2;
if (arr[pos] < arr[mid])
{
pos = mid;
}
if (arr[pos] > arr[end])
{
pos = end;
}
swap(&arr[begin], &arr[pos]);
//left 指向数组的最左边
//right 指向数组的最右边
int left = begin;
int right = end;
//key 默认为 left 位置
int key = arr[begin];
//cur 指向 left + 1 的位置
int cur = left + 1;
while (cur <= right)
{
//三路划分
if (arr[cur] == key)
{
//cur 遇到与 key 相等的值时 cur++
cur++;
}
else if (arr[cur] < key)
{
//cur 遇到比 key 小的值后跟 left 位置进行交换,随后 left++,cur++
swap(&arr[left], &arr[cur]);
left++;
cur++;
}
else
{
//cur 遇到比 key 大的值后跟 right 位置进行交换,随后 right--
swap(&arr[right], &arr[cur]);
right--;
}
}
//二分
QuickSort(arr, begin, left - 1);
QuickSort(arr, right + 1, end);
}
终极大招------自省排序
这个排序方式也是 C++ 标准库中排序方法所采用的方式
算法思路
自省排序它的思路就是进行自我侦察和反省,当快速排序递归的深度太深时( C++ STL 中使用的是深度为2倍排序元素数量的对数值)就说明在当前子序列中,选择基准位置出现了问题,性能在快速退化。那就不要进行快速排序分割递归了,该换为堆排序进行排序
代码实现:
比较复杂,需要一点理解力
//向下调整算法(大根堆)
void AdjustDown(int* arr, int size, int pos)
{
//左孩子位置
int child = pos * 2 + 1;
//向下调整算法,直到左孩子位置大于数组个数
if (child < size)
{
//选出左右孩子中最大的那个孩子
if (child + 1 < size && arr[child] < arr[child + 1])
{
child++;
}
//与当前位置进行比较
//如果左右孩子中最大数大于当前位置的数,就进行交换,并且继续向下调整
//如果左右孩子中最大数小于当前位置的数,表示堆已经调整好了
if (arr[child] > arr[pos])
{
//交换两个数的位置
swap(&arr[pos], &arr[child]);
//继续向下调整
AdjustDown(arr, size, child);
}
}
}
//堆排序------升序
void HeapSort(int* arr, int size)
{
//从后往前依次调整建堆
//先让节点的左右子树为堆,然后整体为堆
for (int i = size - 1; i >= 0; i--)
{
//向下调整建堆
AdjustDown(arr, size, i);
}
//堆排序
//排升序要建小堆
//排降序要建大堆
for (int i = 0; i < size; i++)
{
//堆顶与最后一个有效元素交换位置
swap(&arr[0], &arr[size - 1 - i]);
//向下调整,保持堆的结构
AdjustDown(arr, size - i - 1, 0);
}
}
//快速排序------自省排序
void _QuickSort(int* arr, int begin, int end, int deep, int maxdeep)
{
if (begin >= end)
{
return;
}
//当到达指定的最大深度时,改变使用堆排序
if (deep > maxdeep)
{
HeapSort(arr + begin, end - begin + 1);
return;
}
//三数取中
int pos = begin;
int mid = (begin + end) / 2;
if (arr[pos] < arr[mid])
{
pos = mid;
}
if (arr[pos] > arr[end])
{
pos = end;
}
swap(&arr[begin], &arr[pos]);
//基准位置
int key = begin;
//始终指向比基准小的位置,后续与 cur 交换
int prev = begin;
//探路,找比基准值小的数
int cur = prev + 1;
while (cur <= end)
{
//找到比基准值小的数,与 prev 先加一在交换
if (arr[cur] <= arr[key])
{
//慢指针先加一
prev++;
swap(&arr[cur], &arr[prev]);
}
cur++;
}
//基准与prev交换
swap(&arr[key], &arr[prev]);
//二分
_QuickSort(arr, begin, prev - 1, deep + 1, maxdeep);
_QuickSort(arr, prev + 1, end, deep + 1, maxdeep);
}
void QuickSort(int* arr, int begin, int end)
{
int maxdeep = 0;
//计算数组个数
int n = end - begin + 1;
//确定最大深度
for (int i = 1; i < n; i *= 2)
{
maxdeep++;
}
//自省排序,C++ STL 中使用的是深度为2倍排序元素数量的对数值
_QuickSort(arr, begin, end, 0, maxdeep * 2);
}