【问题标题】:Quick Sort not sorting numbers of high range快速排序不排序高范围的数字
【发布时间】:2015-09-03 14:40:09
【问题描述】:
void quickSort(vector<long>& numberList,long low, long high){
    long pivot, indexLow, indexHigh, temp;

    if(low<high){
        pivot = low;
        indexLow = low;
        indexHigh = high;

        while(indexLow<indexHigh){
            while(numberList[indexLow] <= numberList[pivot]){
            indexLow++;
            }
            while(numberList[indexHigh] > numberList[pivot]){
            indexHigh--;
            }

            if(indexLow<indexHigh){
                temp = numberList[indexLow];
                numberList[indexLow] = numberList[indexHigh];
                numberList[indexHigh] = temp;
                }
        }

        temp = numberList[pivot];
        numberList[pivot] = numberList[indexHigh];
        numberList[indexHigh] = temp;

        quickSort(numberList, low, indexHigh-1);
        quickSort(numberList, indexHigh+1, high);
    }
}

这段代码完美地将给定的数字列表排序到 10000,但我尝试使用 100000 并且程序被终止,你能告诉我我做错了什么吗?

【问题讨论】:

  • 由于大量递归,可能会耗尽堆栈空间。
  • 可能是堆栈溢出。

标签: c++ algorithm vector quicksort


【解决方案1】:

您的程序中肯定有错误:http://ideone.com./W0Chni

我只对 2-1-3-8-1-6-8-9-9-9 的 10 个元素进行排序,在崩溃前不久它的 indexLow 为 11。

对不起,它工作正常。

我回应说这可能是堆栈溢出。您可以通过采用不同的支点来稍微减轻这种可能性(无论如何您都应该这样做),但避免堆栈溢出的最佳方法是使用堆栈数据结构而不是递归:

  1. 将初始 {low,high} 推入堆栈
  2. 将您的函数包装在一个while循环中,直到堆栈为空。每次迭代都会将顶部的 {low,high} 弹出堆栈。
  3. 您无需递归,而是将成对的 {low,high} 推入堆栈。

像这样:http://ideone.com/gwWSjV

void quickSort(std::vector<long>& numberList, long low, long high)
{
    struct lh { long low; long high; };
    std::vector<lh> stack;
    stack.push_back(lh{low, high});

    while (!stack.empty())
    {
        lh popped = stack.back();
        stack.pop_back();
        low = popped.low;
        high = popped.high;

        long pivot, indexLow, indexHigh, temp;

        if(low<high){
            pivot = low;
            indexLow = low;
            indexHigh = high;

            while(indexLow<indexHigh){
                while(numberList[indexLow] <= numberList[pivot]){
                indexLow++;
                }
                while(numberList[indexHigh] > numberList[pivot]){
                indexHigh--;
                }

                if(indexLow<indexHigh){
                    temp = numberList[indexLow];
                    numberList[indexLow] = numberList[indexHigh];
                    numberList[indexHigh] = temp;
                    }
            }

            temp = numberList[pivot];
            numberList[pivot] = numberList[indexHigh];
            numberList[indexHigh] = temp;

            //quickSort(numberList, low, indexHigh-1);
            stack.push_back(lh{low, indexHigh-1});

            //quickSort(numberList, indexHigh+1, high);
            stack.push_back(lh{indexHigh+1, high});
        }
    }
}

Voilà:不再递归,对 1,000,000 个元素进行排序完全没有问题。

您可以通过仅将第二个递归推入堆栈然后立即循环返回以执行第一个递归而不进行推入/弹出来优化它,但这并不是一个巨大的收益。

【讨论】:

    【解决方案2】:

    虽然它可能像其他人所说的那样是堆栈溢出,但我对此表示怀疑。您的代码有一些错误可能导致它访问数组中的超出范围的位置,这将(可能,但不能保证)提示提前终止分段错误(或者在其他情况下它似乎工作正常,这就是为什么 UB太糟糕了)。

    考虑一下:

    while(numberList[indexLow] <= numberList[pivot]){
        indexLow++;
    }
    while(numberList[indexHigh] > numberList[pivot]){
        indexHigh--;
    }
    

    如果数组中的每个数字都已经小于或等于numberList[pivot] 怎么办? indexLow 将愉快地递增超过 high,这很可能是数组的大小。您需要检查两个循环是否仍然存在外部循环条件。所以,改为这样做:

    while (indexLow < indexHigh && numberList[indexLow] <= numberList[pivot]) {
        indexLow++;
    }
    while (indexHigh > indexLow && numberList[indexHigh] > numberList[pivot]) {
        indexHigh--;
    }
    

    这确保了内部循环不会使外部条件无效;没有这个,所有关于你的代码为什么会破坏/不工作的赌注都没有了。

    然后我们有这个:

    temp = numberList[pivot];
    numberList[pivot] = numberList[indexHigh];
    numberList[indexHigh] = temp;
    

    现在,如果您按照我所说的那样修复循环,这可能会出现问题。循环可能已经停止,因为每个元素都小于或等于枢轴(在这种情况下,执行此交换操作是安全的),但循环可能已经停止,因为 indexLowindexHigh 发生碰撞,并且在那如果我们不知道numberList[indexLow] 是否实际上大于枢轴,或者它是否仍然小于或等于枢轴。所以我们需要手动测试它,并可能减少 indexLow 以找到与枢轴交换的值:

    assert(indexLow == indexHigh);
    assert(indexLow > low);
    
    if (numberList[indexLow] > numberList[pivot])
        indexLow--;
    
    assert(numberList[indexLow] <= numberList[pivot]);
    
    temp = numberList[pivot];
    numberList[pivot] = numberList[indexLow];
    numberList[indexLow] = temp;
    
    quickSort(numberList, low, indexLow-1);
    quickSort(numberList, indexLow+1, high);
    

    这是包含这些修复的完整版本:

    void quickSort(vector<long> &numberList, long low, long high) {
        long pivot, indexLow, indexHigh, temp;
        if (low<high) {
            pivot = low;
            indexLow = low;
            indexHigh = high;
    
            while (indexLow < indexHigh) {
                while (indexLow < indexHigh && numberList[indexLow] <= numberList[pivot]) {
                    indexLow++;
                }
                while (indexHigh > indexLow && numberList[indexHigh] > numberList[pivot]) {
                    indexHigh--;
                }
    
                if (indexLow < indexHigh) {
                    temp = numberList[indexLow];
                    numberList[indexLow] = numberList[indexHigh];
                    numberList[indexHigh] = temp;
                }
            }
    
            assert(indexLow == indexHigh);
            assert(indexLow > low);
    
            if (numberList[indexLow] > numberList[pivot])
                indexLow--;
    
            assert(numberList[indexLow] <= numberList[pivot]);
    
            temp = numberList[pivot];
            numberList[pivot] = numberList[indexLow];
            numberList[indexLow] = temp;
    
            quickSort(numberList, low, indexLow-1);
            quickSort(numberList, indexLow+1, high);
        }
    }
    

    请注意,此实现比平时复杂得多。像这样在阵列中前后移动并不会真正获得太多收益。传统的实现代码更简单,更容易阅读和理解:

    void quicksort_simpler(vector<long> &numberList, long low, long high) {
        if (low >= high)
            return;
    
        long pivot = low;
        long last = pivot;
        long i;
    
        for (i = pivot+1; i <= high; i++) {
            if (numberList[i] <= numberList[pivot]) {
                last++;
                swap(numberList[last], numberList[i]);
            }
        }
    
        swap(numberList[last], numberList[pivot]);
    
        quicksort_simpler(numberList, low, last-1);
        quicksort_simpler(numberList, last+1, high);
    }
    

    确保包含&lt;algorithm&gt; 以获取swap() 的声明。

    【讨论】:

      【解决方案3】:

      快速排序获得良好的pivot非常重要。您正在使用第一个元素(实际上是您的 low):

      pivot = low;
      

      因此,您将获得深度为 100000 的递归,因此它的堆栈溢出。

      【讨论】:

        【解决方案4】:

        对于 10^5 个元素,有太多的递归例程调用将超出函数堆栈容量并导致堆栈溢出。就像最坏的快速排序情况一样(当所有元素都已经排序O(n^2)),递归关系是T(n) = T(n - 1) + Θ(n),这肯定会导致堆栈溢出。实际上,在最佳/平均情况下,10^5 也足以导致堆栈溢出 (O(n logn))。如果您的容器太大,请改用迭代方法进行快速排序。

        【讨论】:

        • 一般情况绝对不会导致堆栈溢出。有O(log(N))递归调用,每次调用使用O(1)内存;假设在每次调用中使用 32 字节的堆栈空间来获取内务信息和局部变量(这是慷慨的),并且假设可用的堆栈空间是 4 MB(这是保守的),那么您需要多达 128 次递归调用吹堆栈。这意味着该数组有 2^128 个元素 - 没有机会。请注意,快速排序的空间复杂度是O(log(N))(递归深度),不是 O(n log(n)),这是时间复杂度。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多