快速排序和归并排序一般都是用递归来实现的,但是掌握非递归也是很重要的,说不定在面试的时候面试官突然问你快排或者归并非递归实现,递归有时候并不好,在数据量非常大的时候效率就不好,但是使用非递归结果就不一样了,总之各有各的好。
目录
快速排序非递归实现
要实现快排的非递归需要借助栈,将区间存入栈中,在快排的递归中先是对整个区间单趟排序,把key值放到最终位置,在非递归中与递归的思想不变,先手搓一个栈,当然这里博主以前写过,直接复制粘贴就行,有了栈就先对栈进行初始化,将整个区间压栈,先压左区间,再压右区间,根据栈的特性“先进后出”,在一个循环中出栈再压栈,每次出栈和压栈都是一个区间,出栈就对该区间进行排序,排序好后就返回该一个值,这个值就是排序好的值的下标,以该下标为分界分为两个区间,再开始进行排序,循环往复,直到栈中没有数据退出循环,最后销毁栈。
int PartSort(int* a, int left, int right)
{
if (left >= right)
return -1;
int prev = left, cur = left + 1;
int keyi = left;
while (cur <= right)
{
if (a[cur] < a[keyi])
{
prev++;
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[keyi], &a[prev]);
return prev;
}
void PartSortR(int* a, int left, int right)
{
ST st;
STInit(&st);
STPush(&st, left);
STPush(&st, right);
int begin = left, end = right;
while (!STEmpty(&st))
{
end = STTop(&st);
STPop(&st);
begin = STTop(&st);
STPop(&st);
int keyi = PartSort(a, begin, end);//对一个区间单趟排序
if (keyi+1 < end)
{
STPush(&st, keyi + 1);
STPush(&st, end);
}
if (keyi > begin)
{
STPush(&st, begin);
STPush(&st, keyi);
}
}
STDestory(&st);
}
在实现快排非递归时,单趟排序可以用hoare版本或者挖坑法以及前后指针法,这里使用的是前后指针法,在出栈和压栈都是两个数据,代表区间。
归并排序非递归实现
非递归的归并排序和递归归并排序的思想一样,将一个大的问题分成一个一个小问题,将一串数据先通过分解来分成一个一个,再进行合并,在合并过程中比较大小,再放入一个新的数组中,最后把新数组拷贝到原来的数组,而非递归也是如此,只不过不需要和递归一样分解,我们直接设置间距gap,一开始gap等于1,每个数据单独为一组,每个数据进行比较,放入新数组,gap等于2,每两个数据为一组,与下一组进行比较,每次gap需要乘以2,直到gap大于或者等于数据个数就结束,对数组的拷贝可以是在全部结束之后拷贝,也可以是部分拷贝只不过要拷贝多次。
因为gap每次都是乘以2,所以在数据个数不是2的n次方时会出现越界访问,需要对该问题解决,可以通过调试解决,但是这里有一个小技巧,我们可以打印出区间,更好的看出哪个区间出现了越界访问。
错误代码:
void MergesortR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
return;
}
int gap = 1;
while (gap < n)
{
int j = 0;
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i - 1 + gap;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[j++] = a[begin1++];
}
else
{
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
}
printf("\n");
memcpy(a, tmp, sizeof(int) * n);
gap *= 2;
}
free(tmp);
}
这里数据是9个,看运行结果:
这里可以看出gap为不同值时比较的区间,这里有三种情况:
1、begin2越界访问了,begin2越界end2肯定也越界了。
2、end1越界了,begin2和end2肯定也越界了。
3、end2越界了。
所以只需要根据这三种情况对症下药:
1.当begin2越界了,就把begin2调成end2大的数就可以,因为end2 大于begin2就不会进入下面的循环,也就不会越界访问。
2.当end1越界了,就把end1调成边界值就可以,begin2 和 end2 就和第一种情况一样的解决方法。
3.end2越界了就只需要将end2调成边界就好了。
调整代码:
void MergesortR(int* a,int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (tmp == NULL)
{
perror("malloc fail");
return;
}
int gap = 1;
while (gap < n)
{
int j = 0;
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i - 1 + gap;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
if (end1 > n - 1)
{
end1 = n-1;
begin2 = n + 1;
end2 = n;
}
else if (end2 > n - 1)
{
end2 = n - 1;
}
else if (begin2 > n - 1)
{
end2 = n;
begin2 = n + 1;
}
//printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[j++] = a[begin1++];
}
else
{
tmp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
}
//printf("\n");
memcpy(a, tmp, sizeof(int) * n);
gap *= 2;
}
free(tmp);
}
运行结果:
最后不要忘记释放向操作系统申请的空间,有借有还才行。
计数排序
思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中
代码展示:
//计数排序
void Countsort(int* a, int n)
{
int max = a[0], min = a[0];
for (int i = 0; i < n; i++)
{
if (max < a[i])
{
max = a[i];
}
if (min > a[i])
{
min = a[i];
}
}
int k = max - min+1;
int *CountA = (int*)calloc(k, sizeof(int));
if (CountA == NULL)
{
perror("calloc fail");
return;
}
for (int i = 0; i < n; i++)
{
CountA[a[i] - min]++;
}
int j = 0;
for (int i = 0; i < k; i++)
{
while (CountA[i]--)
{
a[j++] = i + min;
}
}
}
写计数排序最好不要写绝对位置,相对位置是最好的,绝对位置就是1就是在新数组对应的就是下标为1的位置,当数据中有负数时就会出现越界 ,相对位置就是在统计某个值时,放入新数组时将该值减去这组数据中的最小值,所以需要找出该组数据中的最大和最小值,那么新数组的大小就是最大值减去最小值加一,当统计数据出现的个数时需要减去最小值,因为最小值减去最小值等于0,也就是新数组的下标范围是0~最大值减去最小值。当完成以上操作就可以开始排序了,从新数组起始位置开始也就是0,将下标值加上最小值放入原来数组,循环次数就是在该下标对应值,如果CountA[i]等于0,说明i+最小值这个值不存在。
计数排序的特性总结:
1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
2. 时间复杂度:O(MAX(N,范围))
3. 空间复杂度:O(范围)