java 使用快速排序对大列表进行排序时,总是会发生堆栈溢出

kgsdhlau  于 2023-11-15  发布在  Java
关注(0)|答案(2)|浏览(171)

我正在做一个快速排序的分配,但它总是抛出一个堆栈溢出排序大列表时(>100.000项目)。堆栈溢出应该表明停止条件是不正确的,但我找不到问题。我真的很感激一些帮助。感谢阅读。我的快速排序:

private void qSort(List<E> items, int low, int high, Comparator<E> comparator) {
    // base case
    if (low >= high) {
        return;
    }

    // set the pivot (last element)
    E pivot = items.get(high);

    int leftPointer = low;
    int rightPointer = high;

    while (leftPointer < rightPointer) {
        // we move the left pointer until we find a value that is bigger than the pivot
        while (leftPointer < rightPointer && comparator.compare(items.get(leftPointer), pivot) <= 0) {
            leftPointer++;
        }

        // we move the right pointer until we find a value that is smaller than the pivot
        while (leftPointer < rightPointer && comparator.compare(items.get(rightPointer), pivot) >= 0) {
            rightPointer--;
        }

        // swap the values at the pointers
        swap(items, leftPointer, rightPointer);

    }
    // at this point, leftPointer and rightPointer are equal and we need to swap the item at the pointers with the pivot
    swap(items, leftPointer, high);

    // now we have the pivot is in the correct position
    // we continue recursively sorting the left side of the pivot, (not including the pivot, because its already in its final position)
    qSort(items, low, leftPointer - 1, comparator);
    // and the right side of the pivot
    qSort(items, leftPointer + 1, high, comparator);

}

字符串
我尝试了各种不同的实现,没有一个工作。我试图增加堆栈大小。

jm2pwxwz

jm2pwxwz1#

如果你的数据包含排序的运行,那么n项的快速排序将需要O(n)堆栈帧。即使只有一些排序的运行,这也可能是一个问题。
有几种解决方案。最简单的方法是在使用它之前简单地将枢轴与随机元素交换。现在您将在生成O(n)伪随机选择时执行O(n)工作。但是现在几乎不可能触发快速排序的潜在病态行为。
目前最常用的解决方案是使用另一种排序算法。Timsort是一种流行的选择,因为它的最坏情况行为是O(n log(n)),但它可以在许多常见的真实的世界场景中找到并利用排序序列来获得时间O(n)

hrirmatl

hrirmatl2#

我在on my blog前写过关于实现快速排序的陷阱。看看你的实现:

  • 您似乎正在使用Lomuto分区方案这意味着有许多重复的数组将触发快速排序的最坏情况,其中一个分区是微不足道的(单个元素/无元素),而另一个分区包含所有剩余的元素。这可能需要数组长度的线性堆栈大小,导致堆栈溢出。我建议切换到 * 三路分区 *(“荷兰标志排序”),将元素分为小于、等于和大于枢轴的元素;然后您只需递归排序“较小”和“较大”分区。
  • 我们总是选择最后一个元素作为pivot。这意味着对于排序数组,最坏的情况会发生:一个分区是平凡的,另一个包含除了一个之外的所有元素。一个简单的解决方案,正如其他人已经指出的那样,是随机选择pivot;这样,你可以实现预期的O(n log n)运行时间。
  • 此外,你可以--也应该--实现一个简单的技巧来限制堆栈的使用:* 先对较小的分区进行排序 *,然后通过tail调用对另一个分区进行排序,不使用堆栈空间。不幸的是,Java没有合适的tail调用,所以你必须选择迭代实现:使用“作业”堆栈,其中Job类只是数组“切片”/范围(两个整数:开始时,推送Job(0,n),其中n是数组长度,然后通过推送作业进行“递归调用”(from,to)。迭代实现将完全避免堆栈溢出,但您可能仍然希望限制辅助空间的使用-为此,请首先推送较大的作业。这背后的基本原理是,我们希望较小的作业位于顶部,因为我们可以使用较少的辅助空间完成它们;可以证明,如果您这样做,您的作业堆栈将最多具有O(log n)大小。

如果你可以自由地实现另一种排序算法,我推荐合并排序,我认为这是最简单的有效排序算法(假设你允许使用线性辅助空间)或堆排序(这是就地排序,也达到了O(n log n)的时间复杂度,但仍然很简单)。

相关问题