引言:
今天因为要写论文,所以现在有点迟了,并且此时是北京时间:2022/12/28/1:41 ,我发现晚睡我真的是专业的,当然睡觉我也是专业的,懂的都懂,现在有点迟加上天大寒,手指不可屈伸,所以我们的引言就这样啦!但是这个位置我还想要记录一下:今天我的搜狗输入法成功进入20万字了,电脑上自带的键盘都要给我敲烂了,我已经能听出来空格键的声音跟以前不一样了,但是还可以用,本来是打算在20万字之时就换一个键盘的,但是……一言难尽,所以暂时我们继续用电脑上的键盘,等我们到40万字的时候再换吧(想象一下,等40万字,可能……)。上篇博客我们讲到了快速排序的递归方法实现,今天我们就讲一下快速排序的非递归实现。
1.快排实现(非递归)
有的人会问,我们不是已经实现了快排吗?快排我已经学会了啊,为什么还要区分递归和非递归呢?
原因就是递归的缺陷,递归是有一定的缺陷的。
递归的缺陷:建立栈帧,效率低(但是这个对于递归基本不算是缺陷) ,在一些极端情况下,栈帧深度(因为调用一个函数就会创建一个栈帧)太深,会导致栈溢出(这个就是递归的真正的大缺陷)
所以我们就要想一些方法来解决这个缺陷,当然解决方法有很多,比如增加栈的空间,使它不会栈溢出,但是这好像是不可能的,所以最好的方法就是不使用递归(当然是在某些特定的情况下不使用)
所以我们就可以想到递归的替换方法,我相信大多数人第一时间都会想到的是用循环来替代递归,但是今天我们不使用循环来替代递归,今天我们使用数据结构中的栈来模拟递归的过程,当然前提是有一个栈,所以我们要先自己实现一个栈。
代码如下:
void Swap(int* child, int* parent)
{
int tmp = *child;
*child = *parent;
*parent = tmp;
}
int PartQuickSort1(int* arr, int left, int right)
{
int index = GetMidIndex(arr, left, right);
Swap(&arr[left], &arr[index]);
int begin = left;
int end = right;
int pivot = begin;
int key = arr[begin];
while (begin < end)
{
while (begin < end && arr[end] >= key)
{
end--;
}
arr[pivot] = arr[end];
pivot = end;
while (begin < end && arr[begin] <= key)
{
begin++;
}
arr[pivot] = arr[begin];
pivot = begin;
}
pivot = begin;
arr[pivot] = key;
return pivot;
}
void QuickSortNonR(int* arr, int n)
{
//此时因为这个需要有一个栈需要我们去拿以前文件中的栈的实现的代码
//所以为了方便,我们把这个非递归的快排的实现给拿到外面去
ST st;
StackInit(&st);
//栈实现的原理:栈里面的区间就是需要被单趟排序的
StackPush(&st, n - 1);//根据栈的原理(后进先出),当我们想要先出我们的左,我们就不应该先入左,而是应该要先入右,此时的右也就是我们的n-1(传参传的是闭区间的话)
StackPush(&st, 0);//根据栈的原理,并且同理的情况下,这边我们要出的是右,所以我们就应该要入左,也就是入0
while (!StackEmpty(&st))//只要栈里面有数据就不结束,只有当我的栈里面的数据为空的时候才停止
{
//此时如上面两步,我已经把我的左子数组和右子数组给放进去了
int left = StackTop(&st);//数组放进去之后,我就开始出数组中的元素(并且此时因为先进后出原理,所以此时我们栈中出的第一个元素就是我的要放在arr[0]的元素了)
StackPopt(&st);//出完元素要删除掉,因为是栈,只有这样我们才可以出剩下的元素
int right = StackTop(&st);
StackPopt(&st);
int keyIndex = PartQuickSort1(arr, left, right);
//此时有了上面这个挖坑法的代码,我们此时就可以拿到中间的那个key了
//所以我们此时就把这个数组给分为了左子区间和右子区间
//[left,keyIndex-1][keyIndex][keyIndex+1,right]
if (keyIndex + 1 < right)
{
//此时就是表示右半区间还存在,元素没有排序完,我们就还需要入栈
StackPush(&st, right);
StackPush(&st, keyIndex + 1);
}
if (left < keyIndex - 1)//这个表示此时的栈
{
//因为上述的代码left是会++的,所以只要left没有走到key的位置处,就是表示此时left中还有元素没有出完
StackPush(&st, keyIndex-1);
StackPush(&st, left);
}
}
StackDestory(&st);
}
此时的void QuickSortNonR(int* arr, int n)
这个代码中的内容就是不使用递归实现快排的代码,而int PartQuickSort1(int* arr, int left, int right)
而这个函数就是一个普通的挖坑法获取key的代码而已,所以重点是void QuickSortNonR(int* arr, int n)
这个函数中的内容,但是前提是我们要有一个数据结构中的栈,所以我们此时就还要实现一个栈出来(C语言的不好的地方,假如是C++或者Java就可以不用,因为它们有自己独立的栈可以供我们直接调用),所以关于栈的知识可以看我以前的博客,讲解的都是非常的到位的。
2.归并排序(非递归)
原理如图:
代码实现:
void MergeSortNonR(int* arr, int n)
{
//归并的思想,我肯定是要有一个临时数组的
int* tmp = (int*)malloc(sizeof(int) * n);
int gap = 1;//表示每一组需要进行归并的数据的个数
while (gap < n)
{
int i;
for (i = 0; i < n; i += 2 * gap)//由图得到
{
//[i,i+gap-1] [i+gap,i+gap*2-1]
//归并算法的代码实现:
int begin1 = i;
int end1 = i + gap - 1;
int begin2 = i + gap;
int end2 = i + gap * 2 - 1;
int index = i;
//此时因为数据的个数,不一定按照整数倍,所以在划分组的时候就可能导致越界,或者不存在
//所以需要修正一下
if (begin2 >= n)
{
begin2 = n + 1;
end2 = n;
}
//end2越界也要修正一下
if (end1 >= n)
{
end1 = n - 1;
}
//end2越界,需要修正后再归并
if (end2 >= n)
{
end2 = n - 1;
}
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] < arr[begin2])
{
tmp[index++] = arr[begin1++];
}
else
{
tmp[index++] = arr[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = arr[begin2++];
}
}
//将新数组中的元素拷贝回原数组
int j;
for (j = 0; j < n; j++)
{
arr[j] = tmp[j];
}
gap *= 2;
}
free(tmp);
}
3.测试代码
时间有点急了,已经快3点多了,所以就这样吧!我要睡了。