在最佳情况下,由于堆栈溢出错误,快速排序算法失败 - C++
Posted
技术标签:
【中文标题】在最佳情况下,由于堆栈溢出错误,快速排序算法失败 - C++【英文标题】:QuickSort algorithm fails due to stack overflow error in best case - C++ 【发布时间】:2014-03-22 18:21:08 【问题描述】:我尝试实现的快速排序算法有问题。
我参加了基础算法课程,我们为实验室作业提供了用于实现各种算法的伪代码。这些算法取自 Cormen 并被同化为 C++ 语言,我们应该验证效率并为其中的分配和比较的数量生成图表。
现在的问题:
下面的代码应该对一个包含 10000 个数字的数组进行快速排序,并在最佳情况下使用它(数组的轴始终位于中间):
int partition(int *a, int p, int r)
int x = a[r];
countOpQS++;
int index = p - 1;
for (int count = p; count <= (r - 1); count++)
if (a[count] <= x)
index += 1;
swap(a[index], a[count]);
countOpQS += 3;
countOpQS++;
swap(a[index + 1], a[r]);
countOpQS += 3;
return (index + 1);
int select(int *a, int p, int r, int index)
if (p == r)
return a[p];
int q;
q = partition(a, p, r);
//countOpQS++;
int k = q - p + 1;
if (index <= k)
return select(a, p, q - 1, index);
else
return select(a, q + 1, r, index - k);
void bestQuickSort(int *a, int p, int r)
if (p < r)
select(a, p, r, (r - p + 1) / 2);
bestQuickSort(a, p, (r - p + 1) / 2);
bestQuickSort(a, ((r - p + 1) / 2) + 1, r);
main函数中的调用是由:
for (index = 100; index <= 10000; index += 100)
countOpQS = 0;
for (int k = 0; k < index; k++)
a[k] = rand();
bestQuickSort(a, 1, index);
out3 << index << ", " << countOpQS << "\n";
使用这些方法应该是可行的,但它在运行时会很快跳入堆栈溢出。我什至在 Visual Studio 中提出了保留堆栈,因为它在进入可能的最坏情况时是必要的(已经有序的数组,随机枢轴)。
你们知道为什么它不起作用吗?
【问题讨论】:
您没有显示数组(或向量)a
的定义位置,但由于您使用 0..index-1 范围内的索引对其进行了初始化,这表明它包含 index
元素。 .. 请注意,您将index
传递给bestQuickSort,后者将其作为r
传递给select
,然后传递给partition
,后者随后引用了a[r],它是a[index],它是末尾的一个你的数组/向量。我会先解决这个问题。
@amdn 数组 a 在主函数中声明。 for 循环只是生成数字并将它们输出到 .csv 文件中。现在有必要吗?可以这样想:我正在获取一个包含 100 个元素(从 0 到 99)的数组。调用中的数字将只是要排序的数组中第一个和最后一个元素的索引...
Best Case scenario (taking the pivot of the array always at the middle)
请注意,这只是输入已经排序(或反向排序)的最佳情况
是的,这是我的一个错误。上面的代码是单独保存的。内部 for 语句看起来像' a[k] = k ',使其已经排序。所以还是一样。
它的深度不应超过 14 层,并且每向下一层应将阵列削减一半左右。在调试器中单步执行。
【参考方案1】:
首先,你应该知道你的函数select()
重新排列了[p, r] 范围内的元素,使得第一个索引处的元素(注意索引是从一开始的! ) 位置是在排序序列中处于该位置的元素,就像std::nth_element
所做的那样。
因此,当您通过select(a, p, r, (r - p + 1) / 2);
选择子数组的中值元素时,中值的索引基于p
。
例如:p = 3, r = 5
,所以(r - p + 1) / 2
为1,中位数会放在a[4]
,也就是说你应该这样调用函数:select(a, 3, 5, 2)
。之后,您只需像这样调用bestQuickSort()
:
bestQuickSort(a, p, (r - p + 1) / 2); // (r - p + 1) / 2 is 1 now!
bestQuickSort(a, ((r - p + 1) / 2) + 1, r);
当然不行!整个代码是:
int select(int *a, int p, int r, int index)
if (p == r)
return a[p];
int q;
q = partition(a, p, r);
//countOpQS++;
int k = q - p + 1;
if(k== index)
return a[q];
else if (index <= k)
return select(a, p, q - 1, index);
else
return select(a, q + 1, r, index - k);
void bestQuickSort(int *a, int p, int r)
if (p < r)
select(a, p, r, (r - p + 1) / 2 + 1); // the index passed to select is one-based!
int midpoint = p + (r - p + 1) / 2;
bestQuickSort(a, p, midpoint - 1);
bestQuickSort(a, midpoint + 1, r);
顺便说一句,您的快速排序版本并不总是在最佳情况下运行,尽管每次您选择(子)数组的确切中位数,但 select
的时间复杂度并不总是 O(n),因为您只需选择a[r]
作为支点,select
的最坏情况性能是二次的:O(n*n)。
【讨论】:
现在我并不是说你在上面所说的对我来说是某种逻辑,但是你的代码让我遇到了比最坏情况更糟糕的情况......你看到的那个计数器是计算函数内完成的分配和操作。对于已经排序的数组,它给我的值比最坏情况高 4-5 倍。 你如何计算最坏情况下的作业?我没有修改你程序的逻辑,只是修复了一些错误。您应该在主函数中将基于 0 的索引传递给bestQuickSort()
,所以它是 bestQuickSort(a, 0, index-1)
。
有些人喜欢在将数组传递给 QuickSort 之前(随机)打乱他们的数组,以避免“已经排序”或“几乎排序”的情况。此外,qsort 的标准实现根据数组大小使用不同的中值选择启发式算法,对于较小的数组,它们甚至会切换到冒泡排序。现在关于堆栈溢出,一个优秀的编译器将通过将递归转换为迭代来消除实际的递归堆栈。例如:***.com/questions/310974/…
@v.oddou 你说的与OP的问题无关,堆栈溢出是由于错误的代码导致无限递归。
@jfly:完全正确。就像 Mike Dunlavey 所说的那样,递归深度应该在 log(N) 周围浮动,所以非常小。以上是关于在最佳情况下,由于堆栈溢出错误,快速排序算法失败 - C++的主要内容,如果未能解决你的问题,请参考以下文章