*
一、非递归版快排
1.使用栈实现非递归版快排
在学习非递归版快排前,建议大家先学习递归版的快排,否则非递归版的快排将很难理解,这里附上本人写的快排的博客解析:【初阶数据结构与算法】八大排序算法之交换排序(冒泡排序,快速排序—hoare、挖坑法、lomuto双指针3种版本)
接下来我们来学习如何使用栈这个数据结构帮我们实现快排,首先我们都知道快排是通过递归实现的,那么问题来了,为什么栈就可以帮我们模拟递归的行为
这就不得不提到递归的本质了,递归是通过函数自己对自己的调用,在内存的栈区开辟新的函数栈帧,执行同样的代码实现递归,此时我们要发现一个重点,就是递归的本质是通过在栈区开辟函数栈帧实现
而栈区的压栈方式与我们数据结构中的压栈类似,所以我们才能通过栈这个数据结构来模拟递归调用的行为,只要理解到这一点,后面的内容将会好懂得多
接着我们来正式实现非递归版的快排,首先我们需要一个栈作为辅助,可以将之前写过的栈导入到当前文件夹进行添加,栈在非递归快排中的任务就是存储下标
因为我们递归划分数组不是真的把它们分开了,而是通过给定左右范围的下标来访问不同区域,以此达到划分数组的效果,所以快排递归最重要的信息是下标,只要我们能将下标保存并能够以类似于递归的方式使用,我们就可以不使用递归实现快排
而栈就可以起到这样的效果,我们将指定数组序列的左右下标存入栈中,为了我们取出来是左右的顺序,我们在入栈时要将右下标先入栈,再入栈左下标,如下:
//非递归版快排
void NonRQuickSort1(int* arr, int left, int right)
{
ST st;
STInit(&st);
STPush(&st, right);
STPush(&st, left);
STDestroy(&st);
这样我们就可以保证我们取出对应下标时先取出左下标,再取出右下标,接着我们就创建一个循环,只要栈不为空,我们就进入循环取出对应的左右下标,简单进行一下判断,如果左右下标不符合要求就跳过后面的处理,如果左右下标没有问题就使用快排子函数对这段区间进行处理,最后返回处理好的基准值下标,如下:
while (!STEmpty(&st))
{
//取出区间的下标
int left = STTop(&st);
STPop(&st);
int right = STTop(&st);
STPop(&st);
//如果取出的下标不需要处理就直接使用continue跳过
if (left >= right) continue;
int keyi = _QuickSort(arr, left, right);
}
接着我们就根据返回的基准值的下标对当前的区间进行划分,然后将这些划分后的下标入栈,要注意的是要从右往左将下标入栈,否则到时候取下标的时候可能左右下标相反的,如下:
//非递归版快排
void NonRQuickSort1(int* arr, int left, int right)
{
ST st;
STInit(&st);
STPush(&st, right);
STPush(&st, left);
while (!STEmpty(&st))
{
int left = STTop(&st);
STPop(&st);
int right = STTop(&st);
STPop(&st);
if (left >= right) continue;
int keyi = _QuickSort(arr, left, right);
//[left, keyi - 1] [keyi + 1, right]
STPush(&st, right);
STPush(