4.b
紧接上篇博客,对hoare版本的快排再进行优化。
1.三数取中:在数组的第一个位置、最后一个位置和最中间的位置选出中间值,作为key
解决:1.数组有序时快排效率慢的问题
2.不会出现随机值数组中恰好挑选出最大或者最小值,影响快排的效率
2.小区间优化:最后三层要使用递归的子区间,均使用插入排序。
解决:1.递归太深会引发栈溢出
2.当最后三层递归的子区间数据更接近有序性,快排的效率反而会低于插入排序。
为什么选择插入排序而不选择冒泡排序?
插入排序适用性强,可局部排序;而冒泡排序需要全部排序即数组整体有序才停止,条件性强。
呈上代码:
//三数取中
int GetMidIndex(int* a, int left, int right)
{
int mid = left + (right - left) / 2;
if (left < mid)
{
if (mid < right)
return mid;
else if (left > right)
return left;
else
return right;
}
else
{
if (mid > right)
return mid;
else if (left < right)
return left;
else
return right;
}
}
// 快速排序前后指针法
int PartSort3(int* a, int left, int right);
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = PartSort2(a, left, right);
if (right - left <= 8)
{
InsertSort(a + left, right - left + 1);
}
else
{
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
}
4.c 挖坑法快排
思路:三数取中求得中间值的下标,再将其位置数据与数组左边第一个数据进行交换。数组左边第一个数据作为key,其位置为坑。Right在数组中从后往前找比key小的数,找到就直接将其赋值给坑位。坑的位置换到Right的位置。Left在数组中从前往后找比key大的数,找到就直接将其赋值给坑位。坑的位置换到Left的位置。最后再将key赋值给Right与Left相遇的坑位。此时找到key在数组中的真实位置。由此划分两个子区间,此过程反复进行确定某一数值在数组中的具体位置。
代码如下:
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[mid], &a[left]);
int key = a[left];
int hole = left;
while (left < right)
{
while (left<right&&a[right] >= key)
{
right--;
}
a[hole] = a[right];
hole = right;
while (left<right&&a[left] <= key)
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
return hole;
}
4.d 前后指针快排法
思路:prev是数组左边第一个元素,cur是prev之后的元素。三数取中求得中间值的下标,再将其位置数据与数组左边第一个数据进行交换。数组左边第一个数据作为key。当初始时,cur++后找到的是连续不断的比key小的值,prev、cur++,之后cur继续找比key小的数(与之前连续不断的比key小的值间隔n个比key大的数),找到后prev再++,prev与cur所处位置的数据进行交换,直到cur指向空。这里一个重要的想法是分cur找到比key小的数时与prev的间隔,如果相邻两者同时++;如果不是,prev++后再进行数据的交换。
代码如下所示:
}
// 快速排序前后指针法--复杂版
//int PartSort3(int* a, int left, int right , int n)
//{
// 优化一:三数取中求keyi
// int keyi = GetMidIndex(a, left, right);
// Swap(&a[keyi], &a[left]);
// keyi = left;
// int prev = left;
// int cur = prev + 1;
// while (cur<left+n)
// {
// cur找到大数
// if (a[cur] >= a[keyi])
// cur++;
// cur找到小数
// else
// {
//
// if (prev + 1 != cur)
// {
// prev++;
// Swap(&a[prev], &a[cur]);
// cur++;
// }
// else
// {
// prev++;
// cur++;
// }
//
// }
// }
// Swap(&a[left], &a[prev]);
// return prev;
//}
//
//---简便版
int PartSort3(int* a, int left, int right)
{
int keyi = GetMidIndex(a, left, right);
Swap(&a[keyi], &a[left]);
keyi = left;
int prev = left;
int cur = prev + 1;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)
Swap(&a[cur], &a[prev]);
++cur;
}
Swap(&a[prev], &a[keyi]);
return prev;
}
4.e 非递归实现快排
思路:要想了解非递归实现快排,就要充分了解递归实现快排的内在核心。递归实现快排需要创建函数栈帧,而函数栈帧中存放的是区间。为模拟实现递归快排,必须要解决区间问题。
快排的区间类似于一个二叉树的结构。那么可以利用栈的先进后出的性质,模拟实现类似于二叉树的连续调用左子区间的形式。
代码实现如下:
需要引用栈的头文件和源文件
6.堆排序