快速排序递归实现
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。将区间按照基准值划分为左右两半部分的常见方式有:
- hoare版本
确定基准后,从左边开始找大于基准值的元素,右边开始找小于基准值的元素,找到后交换两个元素然后继续下一轮寻找,直到左右相遇

代码实现:
void Swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 快速排序hoare版本
int PartSort1(int* a, int left, int right) {
assert(a);
int keyi = right;
while (left < right) {
while (left < right) {//左边开始找小于基准值的
if (a[left] > a[keyi]) {
break;
}
left++;
}
while (left < right) {//右边找大于基准值的
if (a[right] < a[keyi]) {
break;
}
right--;
}
Swap(&a[left], &a[right]);//交换小的与大的
}
Swap(&a[left], &a[keyi]);//左右相遇的位置放入基准值,左边都比他小,右边都比他大
return left;
}
void QuickSort(int* a, int left, int right) {
assert(a);
if (left >= right) {
return;
}
int div = PartSort1(a, left, right);//每一趟放入div位置的数都是最终有序时该数所在的位置
QuickSort(a, left, div - 1);//递归排div位置的左边和右边
QuickSort(a, div + 1, right);
}
- 挖坑法
确定基准后,定义临时变量保存基准值。基准值的位置就相等于一个“坑”然后从左边开始找大于基准值的元素,找到后将该值放入“坑”里,该元素原来的位置就是新的“坑”。然后从右边开始找小于基准值的,找到后放入新的“坑”。重复上述操作,直到左右边界相遇。
代码实现:
int PartSort2(int* a, int left, int right) {
assert(a);
int keyi = right;
int temp = a[keyi];//保存基准值
while (left < right) {
while (left < right) {
if (a[left] > temp) {//左边有比基准值大的,直接与基准值交换位置
a[keyi] = a[left];
keyi = left;
break;
}
left++;
}
while (left < right) {//右边有比基准值小的,交换关键值位置
if (a[right] < temp) {
a[keyi] = a[right];
keyi = right;
break;
}
right--;
}
}
a[keyi] = temp;//最终在基准值的位置放入最初保存的基准值
return left;
}
- 前后指针版本
确定基准后,定义前后两个指针(数组中即两个下标),开始两个指针指向同一个元素,前指针开始遍历元素,元素小于基准值的,前后指针交换元素且一同向下遍历一个元素,前指针遇见大于基准值的前指针向下遍历一个元素,后指针不动,前指针遍历结束时,与后指针的下一个元素交换。基准值放入最终位置。
代码实现:
// 快速排序前后指针法
int PartSort3(int* a, int left, int right) {
assert(a);
int cur = left;
int prev = left - 1;
int keyi = right;
while (cur <= right) {
if (a[cur] < a[keyi] && ++prev != cur) {//当前位置的值小于基准值,perv++,若不等于cur,交换perv与cur的值
Swap(&a[cur], &a[prev]);
}
cur++;//cur每次往后遍历
}
Swap(&a[prev + 1], &a[keyi]);//包括a[prev]之前的值均小于基准值,交换a[prev + 1]与基准值
return prev + 1;
}
优化:
可以看到快排每次取基准值,把数组分为两个区间,然后递归再去分。理想情况下每次都是把一个数组均等分为两个区间,这样效率最高。但实际中往往不会这样,有时还会有最坏的情况,即每次取得基准值都是最大或最小的,这样每排一个基准值数组其实并没有分为两个区间,只是下次在排时少一个元素。所以在选取基准值时要注意不能选到最大或最小的,解决这一问题最简单的方法就是三数取中,一般取数组开始的元素,末尾的元素和中间的元素,取这三个元素中间的值为基准值,这样就避免了最坏的情况。
代码实现:
int GetMidNum(int *a, int left, int right) {//返回三个数中中间的数
int mid = (left + right) / 2;
if (a[left] > a[mid]) {
if (a[mid] > a[right]) {
return mid;
}
else {
return a[left] > a[right] ? right : left;
}
}
if (a[mid] < a[right]) {
return mid;
}
else {
return a[left] > a[right] ? left : right;
}
}
// 快速排序挖坑法
int PartSort2(int* a, int left, int right) {
assert(a);
Swap(&a[GetMidNum(a, left, right)], &a[right]);
int keyi = right;
int temp = a[keyi];//保存关键值
while (left < right) {
while (left < right) {
if (a[left] > temp) {//左边有比关键值大的,直接与关键值交换位置
a[keyi] = a[left];
keyi = left;
break;
}
left++;
}
while (left < right) {//右边有比关键值小的,交换关键值位置
if (a[right] < temp) {
a[keyi] = a[right];
keyi = right;
break;
}
right--;
}
}
a[keyi] = temp;//最终在关键值的位置放入最初保存的关键值
return left;
}
特点:
- 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
- 时间复杂度:O(N*logN)
- 空间复杂度:O(logN)
- 稳定性:不稳定
快速排序非递归实现
其实递归就是多次调用同一个函数,创建多个栈帧。快排的递归就是函数主题部分执行完毕之后,又调用两个递归函数。我们可以借助栈来实现创建栈帧的过程。每调用一次函数,就把该函数放入栈中,执行函数时出栈即可。
代码实现:
// 初始化栈
void StackInit(Stack* ps) {
assert(ps);//对指针判空
ps->_a = (STDataType*)malloc(sizeof(STDataType));//为栈开辟空间
ps->_top = 0;
ps->_capacity = 1;
}
// 入栈
void StackPush(Stack* ps, STDataType data) {
assert(ps);
if (ps->_top == ps->_capacity) {//栈已满,需要增容
//定义临时指针来接受重新开辟的空间,防止数据丢失,每次增容为原来的2倍
STDataType* temp = (STDataType*)realloc(ps->_a, sizeof(STDataType) * ps->_capacity * 2);
if (temp == NULL) {
printf("申请空间失败!\n");//若空间申请失败,则报错且中止程序;
assert(0);
}
else {//申请空间成功,将地址赋值到原指针,栈的空间翻倍
ps->_a = temp;
ps->_capacity *= 2;
}
}
ps->_a[ps->_top] = data;//栈顶位置赋值为data
ps->_top++;//栈顶后移一位
}
// 出栈
void StackPop(Stack* ps) {
if (StackEmpty(ps)) {//如果栈为空,则直接返回
return;
}
ps->_top--;//栈不为空,栈顶前移一位
}
// 获取栈顶元素
STDataType StackTop(Stack* ps) {
if (StackEmpty(ps)) {//如果栈为空,则直接返回
printf("栈为空!\n");
return -1;
}
return ps->_a[ps->_top - 1];//栈不为空,返回栈顶元素
}
// 获取栈中有效元素个数
int StackSize(Stack* ps) {
assert(ps);//对指针判空
return ps->_top;//栈内共有top个有效数据
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps) {
assert(ps);//对指针判空
if (ps->_top) {
return 0;
}
else {//如果top == 0表示栈为空
return 1;
}
}
// 销毁栈
void StackDestroy(Stack* ps) {
assert(ps);
free(ps->_a);//释放申请的空间
}
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right) {
assert(a);
int div = PartSort2(a, left, right);
Stack s;//创建栈,来实现非递归
StackInit(&s);
StackPush(&s, div + 1);
StackPush(&s, right);
StackPush(&s, left);
StackPush(&s, div - 1);//将第一趟排好后,将左右两个区间都压入栈里,先入右栈,类似于先递归左边
while (!StackEmpty(&s)) {//栈不为空时执行循环
int right = StackTop(&s);
StackPop(&s);
int left = StackTop(&s);
StackPop(&s);//取区间
if (left < right) {//判断是否需要排序,如需要,则排序,然后将分割的两个区间继续入栈
div = PartSort2(a, left, right);
StackPush(&s, div + 1);
StackPush(&s, right);
StackPush(&s, left);
StackPush(&s, div - 1);
}
}
}
归并排序递归版
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
图解:

代码实现:
void MergeArr(int* a, int begin1, int end1, int begin2, int end2, int* tmp)
{
int left = begin1, right = end2;//合并两个有序区间
int index = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
tmp[index++] = a[begin1++];
else
tmp[index++] = a[begin2++];
}
while (begin1 <= end1)
tmp[index++] = a[begin1++];
while (begin2 <= end2)
tmp[index++] = a[begin2++];
// 把归并好的再tmp的数据在拷贝回到原数组
for (int i = left; i <= right; ++i)
a[i] = tmp[i];
}
void _MergeSort(int* a, int left, int right, int* tem) {
if (left >= right) {//如果不能再进行分割,返回
return;
}
int mid = (left + right) / 2; //将区间分割为左右两部分,分别递归进行归并排序
_MergeSort(a, left, mid, tem);
_MergeSort(a, mid + 1, right, tem);
MergeArr(a, left, mid, mid + 1, right, tem);
}
// 归并排序递归实现
void MergeSort(int* a, int n) {
assert(a);
int* tem = (int*)malloc(sizeof(int) * n);//创建临时空间保存归并的数组
_MergeSort(a, 0, n - 1, tem);//调用子函数完成归并排序
free(tem);
}
特点:
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定
归并排序非递归版
我们可以直接把数组看为n个区间,省去了递归版的划分区间,然后只考虑合并的问题。合并的时候要考虑到区间总数可能为奇数个,导致有的区间只有第一个区间,没有与之合并的第二个区间,,然后就是选择合并的迭代条件,不断迭代。完成合并。
代码实现:
/归并排序非递归实现
void MergeSortNonR(int* a, int n) {
assert(a);
int* tem = (int*)malloc(sizeof(int) * n);//创建临时空间保存归并的数组
int gap = 1;
while (gap < n) {
for (int i = 0; i < n; i += 2 * gap) {
// [i,i+gap-1] [i+gap, i+2*gap-1]
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
// 1、合并时只有第一组,第二组不存在,就不需要合并
if (begin2 >= n) {
break;
}
// 2、合并时第二组只有部分数据,需要修正end2边界
if (end2 >= n) {
end2 = n - 1;
}
MergeArr(a, begin1, end1, begin2, end2, tem);
}
gap *= 2;
}
free(tem);
}

本文详细介绍了快速排序和归并排序的原理与实现,包括递归与非递归版本,并探讨了不同版本的特点及优化方法。
,归并排序,递归和非递归版本&spm=1001.2101.3001.5002&articleId=113766546&d=1&t=3&u=bf9401512337460da60b4dfd01a909b5)
4435

被折叠的 条评论
为什么被折叠?



