这一篇文章主要讲解快速排序前后指针法和非递归实现,先把代码放到此处
前后指针
cpp
void part2(int* arr, int* pbegin, int* pend,int keyi)
{
int prev = *pbegin;
int cur = *pbegin;
while (cur <= *pend)
{
if (arr[cur] < arr[keyi] && ++prev != cur)
{
Swap(&arr[prev], &arr[cur]);
}
cur++;
}
*pbegin = prev;
}
void QuickSort(int* arr, int left, int right)
{
if (left >= right)
{
return;
}
if (right - left < 10)
{
InsertSort(arr + left, right - left + 1);
return;
}
int keyi = Mid_Search(arr, left, (left + right) / 2, right);
Swap(&arr[keyi], &arr[left]);
keyi = left;
int begin = left;
int end = right;
//part1(arr, &begin, &end, keyi);
part2(arr, &begin, &end, keyi);
Swap(&arr[begin], &arr[keyi]);
keyi = begin;
QuickSort(arr, left, keyi - 1);
QuickSort(arr, keyi + 1, right);
}
非递归
cpp
void _QuickSortNon(int* arr, Stack* ps)
{
while (!StackEmpty(ps))
{
int left = StackTop(ps);
StackPop(ps);
int right = StackTop(ps);
StackPop(ps);
if (left >= right)
{
return;
}
if (right - left < 10)
{
InsertSort(arr + left, right - left + 1);
return;
}
int keyi = Mid_Search(arr, left, (left + right) / 2, right);
Swap(&arr[keyi], &arr[left]);
keyi = left;
int begin = left;
int end = right;
//part1(arr, &begin, &end, keyi);
part2(arr, &begin, &end, keyi);
Swap(&arr[begin], &arr[keyi]);
keyi = begin;
//[left,keyi][keyi+1,right]
if (keyi + 1 < right)
{
StackPush(ps, right);
StackPush(ps, keyi + 1);
}
if (left < keyi)
{
StackPush(ps, keyi);
StackPush(ps, left);
}
}
}
void QuickSortNon(int* arr, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, right);
StackPush(&st, left);
_QuickSortNon(arr, &st);
StackDestroy(&st);
}
前后指针法是另一种快速排序的实现方式,但是虽然换了一种方式,效率却相较之前没有提升,只是变得容易理解。
顾名思义前后指针法核心是前后指针,prev和cur两个指针,首先假设在不三数取中的情况下,把left当作keyi,prev和cur也初始化为left,cur判断自己值是否小于keyi对应的值,若小,则prev++,并交换prev与cur的对应值,反之cur++,prev不做处理,直到cur超出自己访问区间,将prev值与keyi交换,接着分治进行下一组排序。
相比于上一种写法,前后指针显然好理解一点。
接下来讲一讲非递归,为啥要有非递归?
假设数组有序,那我们快排逻辑(在不三数取中前提下)就会无休止的调用堆栈,导致栈溢出。
因为好比你用上一讲的快排方法,end找小于key的值怎么也找不到,所以一次递归就解决了第一个数,递归的深度相当高。
而非递归使用栈这数据结构,是在堆区中开辟空间,堆区的空间相当大,不太可能会发生溢出的情况。
然后是代码实现,首先需要栈这一个数据结构吧,然后我们之前递归的本质其实是分区间,找到一个相对中间的位置然后分而治之,左区间右区间循环往复,那又是什么划分了左右区间?其实就是我们传的下标,那递归的核心就是递归下标吧,同理,采用非递归我们也要存储区间下标,由于下标有左右两边,所以应该一次存储两个,一次弹出两个,直到栈为空就不再进行。
栈为啥会为空?其实也好理解,当我们出现区间就剩一个元素,或者区间一个元素都没有的情况,是不能入栈的,那么这次就相当于只出不进了,因此循环往复栈是会为空的。