QuestSo排序算法由于栈溢出错误而失败,在最佳情况下-C++

QuestSo排序算法由于栈溢出错误而失败,在最佳情况下-C++,c++,performance,algorithm,stack-overflow,C++,Performance,Algorithm,Stack Overflow,我试图实现的快速排序算法有一个问题 我修了一门基本算法的课程,我们为实验室作业提供了各种算法实现的伪代码。这些算法取自Cormen,并被同化为C++语言,我们应该验证效率,并生成图中的赋值和比较数。 现在的问题是: 下面的代码应该对10000个数字的数组进行快速排序,并在最佳情况下使用它,使数组的轴始终位于中间: int partition(int *a, int p, int r) { int x = a[r]; countOpQS++; int index = p -

我试图实现的快速排序算法有一个问题

我修了一门基本算法的课程,我们为实验室作业提供了各种算法实现的伪代码。这些算法取自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";
    }
使用这些方法应该是可行的,但它在运行时会很快跳入堆栈溢出。我甚至在VisualStudio中提升了保留堆栈,因为这是一种必要的操作,而在最坏的情况下,可能是已经排序好的数组random pivot


你们知道为什么它不起作用吗?

首先,你们应该知道你们的函数select会重新排列[p,r]范围内的元素,这样索引TH处的元素就可以注意到索引是基于一的!position是排序序列中位于该位置的元素,就像std::nth_元素一样。 因此,当您通过选择a,p,r,r-p+1/2;,选择子阵列的中间元素时;,中位数指数基于p。 例如:当p=3,r=5,因此r-p+1/2为1时,中位数将放在[4]中,这意味着您应该像这样调用函数:selecta,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);
    }
}

顺便说一句,您的quicksort版本并不总是在最佳情况下运行,尽管每次您选择子阵列的精确中值时,select的时间复杂性并不总是开启的,因为您只选择a[r]作为轴心,select的最坏情况性能是二次的:On*n。

您不显示数组或向量a的定义位置,但是,由于您使用0..index-1范围内的索引初始化它,表明它包含索引元素。。。请注意,您将索引传递给bestQuickSort,bestQuickSort将其作为r传递给select,然后传递给partition,partition将引用[r],这是一个[index],位于数组/向量末尾。我首先要解决这个问题。@amdn数组a在主函数中声明。for循环只生成数字并将其输出到.csv文件中。现在有必要吗?可以这样想:我取了一个100个元素的数组,从0到99。调用中的数字将是数组中第一个元素和最后一个元素的索引。最好的情况是,数组的支点总是在中间注释,这是最好的情况,如果输入已经排序或排序顺序颠倒了,这对我来说是个错误。上面的代码是单独保存的。for语句的内部看起来像'a[k]=k',这使得它已经被排序。所以它仍然是一样的。它的深度不应该超过14层,每一层都应该将阵列切成两半。在调试器中逐步完成。现在我并不是说你在上面所说的对我来说是某种逻辑,但你的代码让我遇到了比最坏情况更糟的情况。。。您看到的计数器用于计算函数中的赋值和操作。对于已经排序的数组,它给出的值比最坏情况高出4-5倍。在最坏情况下,如何计算赋值?我没有修改你程序的逻辑,只是修正了一些错误。您应该在主函数中将基于0的索引传递给bestQuickSort,因此它是bestQuickSorta,0,index-1。有些人喜欢在将数组传递到QuickSort之前随机洗牌,以避免已经排序或几乎排序的情况。此外,qsort的标准实现根据数组大小使用不同的中值拾取启发式,对于较小的数组,它们甚至会切换到气泡排序。现在,关于堆栈溢出,一个伟大的编译器将通过将递归转换为迭代来消除递归的实际堆栈。例如:@v.oddou您所说的与OP的问题无关,堆栈溢出是由于错误的代码导致的无限递归。@jfly:没错。正如Mike Dunlavey所说,递归深度应该在logN周围浮动,所以无论如何都非常小。