我们重点在非递归,这里快排递归的代码入下
void swap(int* a, int* b)//交换
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void InsertSort(int* a, int n)//插入排序
{
assert(a);
for (int i = 0; i < n - 1; i++)
{
int end = i;
int x = a[end + 1];
while (end >= 0)
{
if (a[end] > x)
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = x;
}
}
int GetMid(int* a, int left, int right) //三数去中
{
int mid = left + ((right - left) >> 1);
if (a[left] > a[right])
{
if (a[mid] > a[left])
return left;
else if (a[right] > a[mid])
return right;
else
return mid;
}
else //a[left] <= a[rigth]
{
if (a[mid] < a[left])
return mid;
else if (a[mid] > a[right])
return right;
else
return mid;
}
}
int partion1(int* a, int left, int rigth)//hoare法
{
int mid = GetMid(a, left, rigth);
swap(&a[left], &a[mid]);
int keyi = left;
while (left < rigth)
{
while (left < rigth && a[rigth] >= a[keyi]) //找小
{
rigth--;
}
while (left < rigth && a[left] <= a[keyi]) //找大
{
left++;
}
swap(&a[left], &a[rigth]); //小的往左边甩,大的往右边甩
}
swap(&a[left], &a[keyi]);
return left;
}
int partion2(int* a, int left, int rigth)//挖坑法
{
int mid = GetMid(a, left, rigth);
swap(&a[left], &a[mid]);
int provit = left;
int key = a[left];
while (left < rigth)
{
while (left < rigth && a[rigth] >= key)
{
rigth--;
}
a[provit] = a[rigth]; //让坑位在右边
provit = rigth;
while (left < rigth && a[left] <= key)
{
left++;
}
a[provit] = a[left];
provit = left; //把坑位放在左边
}
a[provit] = key;
return provit;
}
int partion3(int* a, int left, int rigth)//前后指针法
{
int mid = GetMid(a, left, rigth);
swap(&a[left], &a[mid]);
int key = left;
int prev = left;
int cur = left + 1;
while (cur <= rigth)
{
if (a[cur] >= a[key] && ++prev != cur)
{
swap(&a[cur], &a[prev]);
}
cur++;
}
swap(&a[prev], &a[key]);
return prev;
}
void QuickSort(int* a, int left, int rigth) //快速排序
{
if (left >= rigth)
return;
if (rigth - left + 1 < 10)
{
InsertSort(a + left, rigth - left + 1); //在个数小于10的时,可以采取插入来
//优化递归次数
}
else
{
int key = partion1(a, left, rigth);
QuickSort(a, left, key - 1);
QuickSort(a, key + 1, rigth);
}
}
快排的单趟排序一共有三种排序方式(hoare法,挖坑法,前后指针法)
前两种方法思想差不多,都是右边找小,左边找大,交换;前后指针法是把小的往左边甩,这里不过多介绍,每次单趟排序都是让一个元素拍好序
下面我来看非递归的思想,首先我们知道递归是先单趟排序确定一个元素(key)在递归[left,key-1]区间和[key+1] [rigth],而非递归则是用栈保存这些区间,直到栈空就表示这些区间处理完毕,及数组有序,我们画下图

重复以上操作直到栈空数组有序
代码我们要序构建一个栈:栈的头文件
#pragma once
#include
#include
#include
#include
typedef int StackDataType;
struct Stack
{
StackDataType data;
int* a;
int size;
int capatity;
};
void StackInit(struct Stack* pc); //初始化栈
void StackPush(struct Stack* pc, StackDataType x);//进栈
void StackPop(struct Stack* pc);//弹栈
StackDataType StackTop(struct Stack* pc);//取栈顶元素
bool StackEmpty(struct Stack* pc);//判断栈是否为空
void StackDestroy(struct Stack* pc);//销毁栈
栈的源文件
#include"stack.h"
void StackInit(struct Stack* pc) //初始化栈
{
assert(pc);
pc->a = NULL;
pc->size = pc->capatity = 0;
}
void StackPush(struct Stack* pc, StackDataType x)//进栈
{
assert(pc);
if (pc->size == pc->capatity)
{
int newcapatity = pc->capatity == 0 ? 4 : pc->capatity * 2;
StackDataType* tmp = (StackDataType*)realloc(pc->a,sizeof(StackDataType) * newcapatity);
if (tmp == NULL)
exit(-1);
pc->capatity = newcapatity;
pc->a = tmp;
}
pc->a[pc->size++] = x;
}
void StackPop(struct Stack* pc)//弹栈
{
assert(pc);
if (pc->size == 0)
{
printf("Stack is full\n");
exit(-1);
}
pc->size--;
}
StackDataType StackTop(struct Stack* pc)//取栈顶元素
{
assert(pc);
assert(pc->size >= 0);
return pc->a[pc->size -1];
}
bool StackEmpty(struct Stack* pc)//判断栈是否为空
{
assert(pc);
return pc->size == 0;
}
void StackDestroy(struct Stack* pc)//销毁栈
{
assert(pc);
free(pc->a);
pc->a = NULL;
pc->size = pc->capatity = 0;
}
快排的非递归代码
#include"stack.h"
#include"sort.h"
void QuickNoR(int* a, int left, int rigth)
{
struct Stack s;
StackInit(&s);
StackPush(&s, left);
StackPush(&s, rigth);
while (!StackEmpty(&s))
{
int end = StackTop(&s); //右区间
StackPop(&s);
int begin = StackTop(&s); //左区间
StackPop(&s);
int key = partion1(a, begin, end); //每次在区间里确定一个元素的位置(及元素在数组最终的位置)
if (end > key + 1) //如果有区间大于key + 1,说明还有元素,如果等于1就不用在处理了,等于1及在数组中最终的位置
{
StackPush(&s, key + 1); //先入左区间入栈
StackPush(&s, end); //后入右区间入栈
}
if (begin < key - 1) //同上
{
StackPush(&s, begin);
StackPush(&s, key - 1);
}
}
//PrintSort(a, 10); //这里是打印,大家可以忽略
StackDestroy(&s);
}
我们下面来讲讲归并:
归并递归代码,每次处理完数组都需要存入零时数组

void _MergeSort(int* a, int left, int rigth, int* tmp)
{
if (left >= rigth)
return;
int mid = left + ((rigth - left) >> 1); //这里是求中位数
_MergeSort(a, left, mid, tmp); //递归左区间
_MergeSort(a, mid + 1, rigth, tmp); //递归右区间
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = rigth;
int i = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
for (int j = left; j <= rigth; j++)
{
a[j] = tmp[j];
}
}
void MergeSort(int* a, int n)//归并排序
{
int* tmp = (int*)malloc(sizeof(int) * n);
assert(tmp);
_MergeSort(a, 0, n - 1, tmp); //用子函数递归
free(tmp);
tmp = NULL;
}
归并的非递归难在怎么用迭代控制间距和边界处理,这是十分麻烦的,我们看图

void MergrSortNon(int* a, int n) //归并的非递归
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
return;
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;
int index = i;
// [begin1,end1] [begin2,end2]
if (end1 >= n || begin1 >= n) //如果end1 和 begin1越界则说明不需处理了
{
break;
}
if (end2 >= n) //end2越界一定一定要修正,不然数组不会有序,因为没右处理这个数,图中画的 数是 2 ^ n 个数所以不用考虑越界,那要是9 ,10 ...个数这需要处理
{
end2 = n - 1;
}
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++];
}
for (int j = i; j <= end2; j++) //把零时数组考会原数组
{
a[j] = tmp[j];
}
}
gap *= 2; //间距* 2
}
}