问题描述
我目前正在编写一个算法来分析排序算法。我有很多输入,从 1000 个数字到 1 000 000 个输入。 目前我在使用快速排序功能时遇到了一些问题。由于我输入了 1 000 000 个类似数字(1-10 之间的数字),因此此代码将向我抛出错误 (0xC00000FD)(似乎是堆栈溢出异常)。现在,我不知道该怎么做才能减少递归调用的数量,也不知道如何增加堆栈以便可能有多个递归调用。我附上了快速排序的代码。
void swap(int *xp,int *yp)
{
int temp = *xp;
*xp = *yp;
*yp = temp;
}
int partition (int arr[],int low,int high)
{
int pivot = arr[(low+high)/2];
int i = (low - 1);
for (int j = low; j <= high - 1; j++)
{
if (arr[j] < pivot)
{
i++;
swap(&arr[i],&arr[j]);
}
}
swap(&arr[i + 1],&arr[high]);
return (i + 1);
}
void quicksort(int A[],int l,int h)
{
if (l < h) {
int p = partition(A,l,h);
quicksort(A,p - 1);
quicksort(A,p + 1,h);
}
}
解决方法
如果在递归过程中出现堆栈溢出,则意味着您的递归被破坏了。通常应该避免递归,因为它具有创建缓慢和危险算法的巨大潜力。如果您是一名初级程序员,那么我强烈建议您忘记您曾经听说过递归并停止阅读这里。
唯一可以合理允许的时间是递归调用放在函数的末尾,即所谓的“尾调用递归”。这几乎是编译器可以实际优化并用内联循环替换的唯一递归形式。
如果它不能进行尾调用优化,那么就意味着每次进行递归时实际上都调用了一个函数。这意味着堆栈不断堆积,您还会获得函数调用开销。这既是不必要的缓慢,又是不可接受的危险。因此,您编写的所有递归函数都必须针对目标进行反汇编,以确保代码没有出问题。
由于此代码似乎取自此站点 https://www.geeksforgeeks.org/iterative-quick-sort/,因此他们已经在那里为您描述了代码中的大部分问题。他们在底部有一个“quickSortIterative”函数,这是一个更好的实现。
我认为本教程的目的是向您展示一些损坏的代码(您问题中的代码),然后通过摆脱递归来演示如何正确编写它。
,仅在较小的分区上递归就可以避免堆栈溢出:
void quicksort(int A[],int l,int h)
{
while (l < h) {
int p = partition(A,l,h);
if((p - l) <= (h - p)){
quicksort(A,p - 1);
l = p + 1;
} else {
quicksort(A,p + 1,h);
h = p - 1;
}
}
}
然而,最坏情况的时间复杂度保持在 O(n^2),问题代码中使用的 Lomuto 分区方案存在大量重复值的问题。 Hoare 分区方案没有这个问题(实际上更多的重复会导致更少的时间)。
https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme
快速排序中带有分区逻辑的示例代码:
void quicksort(int a[],int lo,int hi)
{
int p;
int i,j;
while (lo < hi){
p = a[lo + (hi - lo) / 2];
i = lo - 1;
j = hi + 1;
while (1){
while (a[++i] < p);
while (a[--j] > p);
if (i >= j)
break;
swap(a+i,a+j);
}
if(j - lo < hi - j){
quicksort(a,lo,j);
lo = j+1;
} else {
quicksort(a,j+1,hi);
hi = j;
}
}
}