我正在学习C,我尝试了一种递归的快速排序算法。在小输入尺寸下,它按预期工作;使用随机生成的数组,所有测试的大小(最多100,000)都没有问题。使用降序数组,它以某种数组大小(32,506)以某种方式中断(Windows给我一条消息,程序已停止工作)。我的代码中是否有任何错误(例如任何错误的内存分配 - 我不确定我是否正确)或者C在递归调用或其他任何内容中有限制吗?
编辑: 我知道我的Quicksort实现相当天真,并且它对这种输入表现得非常糟糕,但我没想到它会崩溃。
我在Windows 10的命令提示符下使用GCC和MinGW。我不知道如何找出确切的结果,因为尽管Windows告诉我程序已经停止,但我并没有真正收到任何指定的错误消息工作
#include <stdio.h>
#include <stdlib.h>
int partition(int *a, int lo, int hi) {
int i = lo; int j = hi+1; int v,t;
v = a[lo]; //partition element
while (1) {
while (a[++i] < v) {if (i == hi) break;}
while (v < a[--j]) {if (j == lo) break;}
if (i >= j) break;
t = a[j]; a[j] = a[i]; a[i]= t; //swap
}
t = a[lo]; a[lo] = a[j]; a[j]= t;//swap
return j;
}
void quicksort(int a[], int lo, int hi) {
int j;
if (hi <= lo) return;
j = partition(a, lo, hi);
quicksort(a, lo, j-1);
quicksort(a, j+1, hi);
}
int main() {
int len;
for (len = 32000;len < 40000;len+=100) {
printf("New Arr with len = %d\n",len);
int *arr;
arr = (int*) calloc(len,sizeof(int));
int j;
//create descending Array
for (j = 0; j < len; ++j) {
arr[j] = len-j;
}
printf("start sorting\n");
quicksort(arr,0,len-1);
free(arr);
}
}
答案 0 :(得分:2)
对我来说,你的代码在更大的尺寸上失败了(c.370,000个元素)。您可能遇到平台限制(可能由于堆栈溢出而限制递归深度)。如果没有确切的错误信息,当然很难确定。
您的输入集可能是您实施的病态案例 - 请参阅What makes for a bad case for quick sort?
您可以通过更好地选择枢轴来减少递归深度 - 常用技术是获取第一个,中心和最后一个元素的中位数。像这样:
int v0 = a[lo], v1 = a[(lo+hi+1)/2], v2 = a[hi];
/* pivot: median of v0,v1,v2 */
int v = v0 < v1 ? v1 < v2 ? v1 : v0 < v2 ? v2 : v0 : v0 < v2 ? v0 : v1 < v2 ? v2 : v1;
您还可以通过仅针对较小的分区递归来减少递归深度,并使用迭代来处理较大的分区。您可以使用编译器的尾部调用消除器将递归转换为迭代,但如果这不起作用,则您需要自己编写它。类似的东西:
void quicksort(int a[], int lo, int hi) {
while (lo < hi) {
int j = partition(a, lo, hi);
if (j - lo < hi -j) {
quicksort(a, lo, j-1);
lo = j+1;
} else {
quicksort(a, j+1, hi);
hi = j-1;
}
}
}
通过上述更改,我可以对超过十亿个元素的数组进行排序而不会崩溃(我必须进行一些性能改进 - 见下文 - 即使这样,也需要17秒)。
当您发现子数组已经排序时,您可能还希望提前返回。我将此作为练习。
P.S。您main()
中的几个问题:
您不会测试calloc()
的结果 - 您可能应该使用malloc()
代替,因为您仍会编写每个元素:
int *arr = malloc(len * sizeof *arr);
if (!arr) return fprintf(stderr, "allocation failed\n"), EXIT_FAILURE;
这是我最终得到的代码:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int partition(int *a, int i, int j) {
int v0 = a[i], v1 = a[(i+j+1)/2], v2 = a[j];
/* pivot: median of v0,v1,v2 */
int v = v0 < v1 ? v1 < v2 ? v1 : v0 < v2 ? v2 : v0 : v0 < v2 ? v0 : v1 < v2 ? v2 : v1;
while (i < j) {
while (a[i] < v && ++i < j)
;
while (v < a[j] && i < --j)
;
int t = a[j]; a[j] = a[i]; a[i]= t; //swap
}
/* i == j; that's where the pivot belongs */
a[i] = v;
return j;
}
void quicksort(int a[], int lo, int hi) {
while (lo < hi) {
int j = partition(a, lo, hi);
if (j - lo < hi -j) {
quicksort(a, lo, j-1);
lo = j+1;
} else {
quicksort(a, j+1, hi);
hi = j-1;
}
}
}
int main() {
int len = INT_MAX/2+1;
printf("New Arr with len = %d\n",len);
int *arr = malloc(len * sizeof *arr);
if (!arr) return fprintf(stderr, "allocation failed\n"), EXIT_FAILURE;
/* populate pessimal array */
for (int j = 0; j < len; ++j) {
arr[j] = len-j;
}
printf("start sorting\n");
quicksort(arr, 0, len-1);
/* test - is it sorted? */
for (int i = 0; i+1 < len; ++i)
if (arr[i] >= arr[i+1])
return fprintf(stderr, "not sorted\n"), EXIT_FAILURE;
free(arr);
}
答案 1 :(得分:0)
递归太深,无法将其存储在堆栈中。
它必须为每个级别存储int j = partition(..)
。
有一些声明性技术可以最大限度地减少递归堆栈的使用。
例如,将结果作为参数。但是这种情况要比我举一个例子复杂得多。