在之前的文章中,一共介绍了三种方法来实现快速排序,这三种方法最终都是通过递归的方式实现的,但是对于使用递归实现由一些缺陷:
1,递归建立栈帧会有消耗,效率问题;
2,如果数据过多,建立栈帧的深度太深,可能会导致栈溢出,因为系统栈空间一般在M级别。
为了解决上述的问题,也可以采用非递归的来实现快速排序,将数据保存在栈中,借用栈的后进先出原则来获取需要处理的区间的下标,再调用快排的实现函数完成排序。
实现过程:
1,创建一个栈,用来保存数据(这些数据是要排序的数组的下标索引);
2,通过入栈出栈的方式来获取要访问的数组区间的下标,并且可以通过栈的后进先出原则来控制先对左右子序列的那一个序列先进行处理;
3,调用快速排序的实现函数,完成排序。
下面通过一个分解图来说明:
1,创建栈,要知道栈的数据出入都是从栈顶进行,后进来的数据相对于其进来时栈中已经存在的数据先出去。
2,假设有一个10个元素的数组a需要进行排序。
3,入栈:因为我们实现排序时会调用快排实现的函数(int PartSort2(int* a, int begin, int end)),需要其开始和结束位置的下标,所以要把数组a的下标推入栈中。(下面代码实现调用的StackPush函数)
这里是先将9(end)推入,再将0(begin)推入,所以出栈时先出来的是0(begin),后是9(end)。
4,出栈:获取begin和end(下面代码实现调用的StackTop函数),每得到一个栈顶数据后,都要将栈顶数据删除(StackPop函数)。
先出0,得到begin = 0;
再出9,得到end = 9;
5,调用快排实现函数PartSort2(a, begin, end),得到div,div为这次排序中最后基准值的位置,以div为中心,分为左右两个子序列,左子序列的值不大于该基准值,右子序列的值大于该基准值。这里假设div为5:
左右子序列的区间为[begin, div-1]和[div+1, end];因为排序后的div已经到最后排序完成的位置上,所以不用再继续进行排序。
6,将左右子序列的下标入栈,入栈时要注意入栈的顺序,因为后入先出的原因,在之前的递归实现中是先处理左子序列再处理右子序列的,所以在此处保持一致,先处理左,再处理右,所以应该先入右子序列的下标,再入左子序列的下标。
7,再出栈:得到begin = 0;end = 4。
出完后:
8,继续调用快排实现函数PartSort2(a, begin, end),得到div,假设得到的div为1。
对于左子序列,继续分:
9,入栈:这里左子序列的左子序列只有一个元素了,所以不用再继续入栈,则只有入其右子序列的下标。
10,如此反复上面的入栈、出栈、调用过程,最后都处理完后(每一个子序列当输入的begin >= end时结束),就得到最终的排序好的序列。
在这个非递归实现的过程中,借助了栈的后入先出特性来实现了对左右子序列进行处理,并且需要特别注意的也是这个后入先出原则,否则在入栈和出栈时不一致最终获取的begin和end可能会出错。下面是参考代码:
实现快排的代码:
int PartSort2(int* a, int begin, int end)
{
assert(a);
int midIdex = GetMidIndex(a, begin, end);
Swap(&a[midIdex], &a[end]);//将基准值换到最后的位置。
//坑
int key = a[end];
while (begin < end)
{
//左边找比key大的值;
while (begin < end && a[begin] <= key)
{
begin++;
}
//找到填入右边的坑,当前begin处为新的坑;
a[end] = a[begin];
//右边找比key小的值;
while (begin < end && a[end] >= key)
{
end--;
}
//找到填入左边的坑,当前end处为新的坑;
a[begin] = a[end];
}
//最后相遇的位置为坑的位置,把key填入。
a[begin] = key;
return begin;
}
void QuickSortNonR(int* a, int begin, int end)
{
assert(a);
Stack st;
StackInit(&st);
StackPush(&st, end);
StackPush(&st, begin);
while (!StackEmpty(&st))
{
int begin = StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
int div = PartSort2(a, begin, end);
//[begin,div-1][div][div+1,end]
if (div + 1 < end)
{
StackPush(&st, end);
StackPush(&st, div + 1);
}
if (begin < div - 1)
{
StackPush(&st, div - 1);
StackPush(&st, begin);
}
}
StackDestroy(&st);
}
关于栈的代码:
Stack.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int StackDataType;
typedef struct Stack
{
StackDataType* _a;
int _top;//栈顶下标
int _capacity;
}Stack;
//初始化
void StackInit(Stack* pst);
//销毁
void StackDestroy(Stack* pst);
//入栈
void StackPush(Stack* pst, StackDataType x);
//出栈
void StackPop(Stack* pst);
//获取栈顶元素
StackDataType StackTop(Stack* pst);
//获取栈中有效个数
int StackSize(Stack* pst);
//检测是否为空
int StackEmpty(Stack* pst);
Stack.c
//初始化
void StackInit(Stack* pst)
{
assert(pst);
pst->_a = (StackDataType*)malloc(sizeof(StackDataType) * 4);
pst->_top = 0;
pst->_capacity = 4;
}
//销毁
void StackDestroy(Stack* pst)
{
assert(pst);
free(pst->_a);
pst->_a = NULL;
pst->_top = 0;
pst->_capacity = 0;
}
//入栈
void StackPush(Stack* pst, StackDataType x)
{
assert(pst);
if (pst->_top == pst->_capacity)
{
StackDataType* tmp = realloc(pst->_a, sizeof(StackDataType) * (pst->_capacity) * 2);
if (tmp == NULL)
{
perror("StackPush");
exit(-1);
}
pst->_a = tmp;
pst->_capacity *= 2;
}
pst->_a[pst->_top] = x;
pst->_top++;
}
//出栈
void StackPop(Stack* pst)
{
assert(pst);
assert(pst->_top > 0);
pst->_top--;
}
//获取栈顶元素
StackDataType StackTop(Stack* pst)
{
assert(pst);
assert(pst->_top > 0);
//return pst->_a[--pst->_top];//有问题,--操作会改变pst->top的值,下次使用时就不是原来的值了。
return pst->_a[pst->_top - 1];
}
//获取栈中有效个数
int StackSize(Stack* pst)
{
assert(pst);
return pst->_top;
}
//检测是否为空,返回1为空,0为非空
int StackEmpty(Stack* pst)
{
assert(pst);
//if (pst->_top == 0)
// return 1;
//else
// return 0;
return pst->_top == 0 ? 1 : 0;
//return !pst->_top;
}