【问题标题】:Quicksort gives stackoverflow on constant numbers but not on random numbers快速排序在常量上提供stackoverflow,但在随机数上不提供
【发布时间】:2016-02-29 09:39:27
【问题描述】:

当我用rand() 或任何常量值分配数组值时,为什么这个 C++ 代码 sn-p 的行为会有所不同,我感到非常困惑。

const int MIN_SIZE = 10000;
const int MAX_SIZE = 100000;

int main()
{
  for(j = MIN_SIZE; j <= MAX_SIZE; j += MIN_SIZE) {
    int *arrPtr = new int[j];
    for(int i = 0; i < j; i++)
       arrPtr[i] = 1; //When I put rand() here, it works fine but in any constant it gives stack overflow
    quickSort(arr, 0, j - 1);
    delete []arrPtr;
  }
}

上面的代码基本上创建了一个以j 为大小的动态分配数组,它每轮递增MIN_SIZE(10,000),并为每个索引分配一些特定的整数。分配后,它使用快速排序算法进行排序,我将在下面提供我的算法,然后在完成后释放该数组。整个事情重复到MAX_SIZE(100,000)。

这是我的快速排序代码:

void quickSort(int *arr, int front, int rear)
{
    if (front < rear)
    {
        int part = partition(arr, front, rear);
        quickSort(arr, front, part - 1);
        quickSort(arr, part + 1, rear);
    }
}

int partition(int *arr, int front, int rear)
{
    int element = arr[rear];
    int i = front - 1;
    for (int j = front; j<rear; ++j)
    {
        if (arr[j] <= element)
        {
            ++i;
            long temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    long temp = arr[i + 1];
    arr[i + 1] = arr[rear];
    arr[rear] = temp;
    return i + 1;
}

我正在尝试实现快速排序算法,该算法严格使用最后一项作为枢轴。在这种情况下,我面临一个奇怪的问题:当我使用 rand() 函数将数组的每个值分配给一个随机数时,一切正常,但是,当我输入一个常量值时,数组的大小变为高达 4039(当您操作 MAX_SIZE 和 MIN_SIZE 时)然后给出 堆栈溢出 错误。我真的很困惑,为什么这会导致问题,此外,为什么是 4039?

【问题讨论】:

  • 查看partition的返回值。我猜你有一个运行方式递归由于分区返回错误的值,导致快速排序整个数组一遍又一遍。
  • 这是作业吗?如果是这样,那么在'Net 和 SO 上会有很多关于快速排序的帮助。否则...使用vector 和内置的sort 算法。 10 次中有 9 次会比你能写的要快。
  • 当数组的所有元素都相同时,是数组已经排序的极端情况,如果数组已经排序,那么使用快速排序会遇到最坏的情况。 See this for an explanation.
  • @JoachimPileborg 是的,我实际上是故意这样做的,以测试它的性能并收集一些数据交换比较次数等方面的数据。然而,这个问题的发生很有趣。有什么建议吗?
  • @DanielJour 但是为什么在 4039 上,这不是很奇怪吗?你发现我的代码中有什么特殊问题吗?

标签: c++ arrays algorithm sorting quicksort


【解决方案1】:

当以简单的方式实现时,使用最后一个元素作为枢轴元素的快速排序预计会溢出堆栈以获取相等的元素。这就是快速排序的工作原理。这是算法中的“缺陷”。

看看为什么只看递归函数调用是如何创建的。

quicksort(arr, 0, 100)  - will produce the recursive calls
   quicksort(arr, 0, 99); and
   quicksort(arr, 100, 100);

问题是quicksort(arr, 0, 99); 将递归数组中的每个元素。

在您的情况下,您的堆栈已满 4039 个元素。您似乎在每次调用中都有大约 8 个整数的状态,这将提示您堆栈的最大大小。我猜大约 1 MB。

这不是随机整数的情况,因为递归调用的深度将均匀分布在递归的左右部分。这种期望的行为使递归深度接近 log N。对于您的 MAX_SIZE,这是大约 17 的深度,而不是 100000。这就是快速排序被描述为 N log N 算法的原因。第一个 N 来自分区。

【讨论】:

  • 所以我们不应该使用最后一个元素作为枢轴,它最终会导致堆栈溢出......在某些情况下......但是当我们使用 rand() 或简单的常量时有什么不同。当我使用 rand() 分配值时,我的程序运行完美,但是,乍一看,我手动分配的那些常量值在我看来是导致问题的原因。然而,我相信你是对的最后一个枢轴错误。
  • 不递归最大间隔,只为更小的间隔(尾递归优化)
  • @Burak。如果数组包含随机值,则枢轴将不是最后一个元素。
  • 问题是 arr[end] 没有将数组分成两半。例如,我的库std::sort 在遇到过多的递归深度时会切换到不同的排序方法。
  • @JoachimPileborg 你是对的,只有当数组已经以升序方式排序时,它才会成为最后一个元素。那么降序呢?它仍然抛出异常。
【解决方案2】:

具有末端枢轴并将数组拆分为两个的常量数组导致“数组中的元素数量”的递归深度和 O(n^2) 时间。

有很多方法可以解决这个问题。

首先,将数组划分为 3 个组件。 Greater、Less 和 Equal 进行分区。相等介于两者之间。这修复了您遇到的角落案例。它增加了常数因子,但快速排序成本变为 O(n lg m),其中 m 是不同元素的数量作为奖励。

排序数组仍然死得很惨。做一个更好的分区选择器。随机分区使可怕行为的概率接近 0。选择 3(或 2k+1)个元素(可能是随机的)并使用它们的中值是另一种方法。对于确定性的良好行为,在 O(n) 时间内找到介于 30% 和 70% 标记之间的元素的算法称为“5 的中位数”(这不仅仅是取 5 个元素的中位数)。

另一个技巧是对数组进行分区,在较小的分区上递归,在较大的分区上循环。这解决了递归深度问题,但不是运行时问题。

接下来,考虑小数组长度的转义策略。与选择排序相比,对(比如说)8 个元素的快速排序可能严重不理想。一旦你有了逃生策略,你就可以乐观地使用快速而肮脏的快速排序(选择 3 个随机元素作为枢轴等)并跟踪递归深度。如果您通过 2*lg(n) 深度,请转义以可证明正确的快速排序(中位数为 5 以找到枢轴)。当你的元素数少于 8 个(调整这个)时,切换到选择排序。

最后,当您刚刚std::sort 时,可能已经完成了以上所有操作以及更多操作。所以改用它吧。

【讨论】:

  • 关于分区步骤的 cmets。 Hoare 在 '62 中最初的快速排序描述了一个分区步骤越来越小。分隔元素是分隔元素。因此,三分区不是“纯”快速排序。 OP 遇到的问题在论文中被评论为“如果边界的值......则容易出现尴尬的情况”。
【解决方案3】:

如果您总是先递归到较小的一半,并且编译器为第二次调用生成尾递归,则可以保证 O(log(N)) 的堆栈深度。

void quickSort(int *arr, int front, int rear)
{
    if (front < rear)
    {
        int part = partition(arr, front, rear);
        int a, b, c, d;
        if (part - front <= rear - part)
        {
            a = front;
            b = part - 1;
            c = part + 1;
            d = rear;
        }
        else
        {
            a = part + 1;
            b = rear;
            c = front;
            d = part - 1;
        }
        quickSort(arr, a, b);
        quickSort(arr, c, d);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-17
    • 2013-11-22
    相关资源
    最近更新 更多