【问题标题】:Partition in quick sort logical error快速排序逻辑错误中的分区
【发布时间】:2015-02-15 06:34:29
【问题描述】:
int partition(int A[], int low, int high){
    int mid = (low+high)/2;
    int pivot = A[mid];
    while(low <= high) {
        while(low <= high && A[high] >= pivot) {
            high--;
        }
        while (low <= high && A[low] <= pivot) {
            low ++;
        }
        if(low <= high) {
            int tmp = A[low];
            A[low] = A[high]; 
            A[high] = tmp;
            high--;
           low++;
        }
    }
    return mid;
}

void quickSort(int A[], int low, int high) {
    if (low >= high) {
      return;
    }
    int ppos = partition(A, low, high);//does the swapping and returns the pivot
    quickSort(A, low, ppos-1);
    quickSort(A, ppos+1, high);
}

这是我的快速排序函数,带有一个分区来进行交换并返回枢轴,以便快速排序以不同的中点重新调用自身。我对快速排序不太熟悉,但这就是我想出的。

问题是它编译得很好,但是当它运行时它总是崩溃。我的功能是否存在逻辑缺陷?关于如何修复我的函数以便它对随机数组进行快速排序的任何建议?

-编辑- 修复了崩溃错误,但分区无法正确排序,有什么建议可以更改函数以便它对数组进行快速排序?

【问题讨论】:

  • 独立于quickSort 测试partition 函数并确保它有效。如果没有,请使用调试器进行逐步调试。
  • 调试器是您 100% 需要用于除琐碎项目之外的任何事情的工具。请学习编译器提供的那个。

标签: c++ sorting logic quicksort


【解决方案1】:

您的分区函数不正确。在快速排序期间有两种主要的序列分区方法:squeezesweep。前一种方法是您正在尝试的方法,并且以更复杂的算法为代价,与后一种方法相比,交换次数很可能更少。


大扫除

我将首先向您展示更简单的 sweep 方法是什么样的,因为它确实是最容易理解的。一般来说,该算法执行以下操作:

  1. 如果序列长度小于 2,请提前退出。对于长度为 1 的序列,没有什么可划分的。
  2. 在序列中选择一个枢轴值。选择一个减少 O(N^2) 退化条件的机会的枢轴值是快速排序分区的圣杯,我不会在这里介绍它,但是有大量的命令该主题可在线获取。
  3. 用序列中的最后一个值交换枢轴值。
  4. 使用两个索引值遍历序列;读者索引和作者索引。当您向序列前进时,任何“小于”枢轴值的读取器索引值都会被交换到写入器索引处的较低序列,并且写入器索引会增加。
  5. 完成后,写入器索引是枢轴值交换到最终位置并且分区完成的点。返回的结果分区点是写入器索引位置。

这个算法其实更容易用代码理解:

size_t partition(int A[], size_t len)
{
    if (len < 2) // 1.
        return 0;

    std::iter_swap(A+len/2, A+len-1); // 2. 3.
    size_t pvt = 0;

    for (size_t i=0; i<len-1; ++i) // 4.
    {
        if (A[i] < A[len-1])
            std::iter_swap(A + pvt++, A+i);
    }
    std::iter_swap(A + pvt, A+len-1); // 5.

    return pvt;
}

请注意,完全可以想象大于的值可能会被交换多次次,因为在行进期间较低的分区被填满。最终一切都会解决,但最好避免这些额外的交换。这就是 squeeze 方法的目的,如下所示。


挤压

虽然 sweep 有其优点(最值得注意的是简单),但最小化交换不在其中。理想情况下,您仅在确定 两个 值在枢轴值的最终着陆位置的相对两侧不合适时才执行交换。要做到这一点,您需要同时执行从低到高的扫描和从高到低的扫描,并且一旦在不正确的位置找到一个元素,就交换那些时间>。最终,低指数和高指数相遇,这样你就找到了枢轴最终的休息地。

size_t partition(int A[], size_t len)
{
    if (len < 2)
        return 0;

    std::iter_swap(A + len/2, A+len-1);
    size_t low = 0, high = len;

    while (1)
    {
        while (low < high && (A[low] < A[len-1]))
            ++low;

        if (low == high--)
            break;

        while (low < high && !(A[high] < A[len-1]))
            --high;

        if (low == high)
            break;

        std::iter_swap(A+low++, A+high);
    }
    std::iter_swap(A+low, A+len-1);

    return low;
}

这里发生了几件看起来很奇怪的事情。注意减少highsecond 内部while 循环的布尔逻辑。我本可以写(A[high] &gt;= A[len-1]),但我想把一个常见的错误带回家。 criticalhigh 降低的条件在逻辑上inverse 到提升low 的条件。如果low 被提升是因为它的元素严格小于我们这里的主元值,那么high 只有在其元素为not(严格小于主元值)时才能被减少.毫无疑问,当我们按照上面的代码显示时,它是正确的,我根本无法公正地估计特定要求被掩盖并导致神秘地破坏的分区算法的次数。


使用 QuickSort 的示例分区

以上任何一个都可以。对函数进行一些细微的修改以在发生交换时产生输出并对随机打乱的值数组进行排序证明了交换计数的减少。下面在两个分区函数中实现这两种算法,分别标记为sweepsqueeze。它们都在相同的随机序列上变得松散,然后在完全排序的序列上再次变得松散,以​​展示交换计数差异。

#include <iostream>
#include <algorithm>
#include <random>
#include <numeric>

size_t sweep(int A[], size_t len)
{
    if (len < 2)
        return 0;

    std::iter_swap(A+len/2, A+len-1);
    size_t pvt = 0;

    for (size_t i=0; i<len-1; ++i)
    {
        if (A[i] < A[len-1])
        {
            std::cout << "swap: " << A[pvt] << ',' << A[i] << '\n';
            std::iter_swap(A + pvt++, A+i);
        }
    }
    std::iter_swap(A + pvt, A+len-1);

    return pvt;
}

size_t squeeze(int A[], size_t len)
{
    if (len <= 1)
        return 0;

    std::iter_swap(A + len/2, A+len-1);
    size_t low = 0, high = len;

    while (1)
    {
        while (low < high && A[low] < A[len-1])
            ++low;

        if (low == high--)
            break;

        while (low < high && !(A[high] < A[len-1]))
            --high;

        if (low == high)
            break;

        std::cout << "swap: " << A[low] << ',' << A[high] << '\n';
        std::iter_swap(A+low++, A+high);
    }
    std::iter_swap(A+low, A+len-1);

    return low;
}

void quicksort(int A[], size_t len, size_t (*part)(int[], size_t))
{
    if (len < 2)
        return;

    size_t pvt = part(A, len);
    quicksort(A, pvt++, part);
    quicksort(A+pvt, len-pvt, part);
}

int main()
{
    std::random_device rd;
    std::mt19937 rng(rd());

    int ar[31] = {0}, ar2[31];
    std::iota(std::begin(ar), std::end(ar), 1);
    std::shuffle(std::begin(ar), std::end(ar), rng);
    std::copy(std::begin(ar), std::end(ar), std::begin(ar2));

    for (auto x : ar)
        std::cout << x << ' ';
    std::cout << '\n';

    std::cout << "Sweep Algorithm\n";
    quicksort(ar, sizeof(ar)/sizeof(*ar), sweep);

    for (auto x : ar)
        std::cout << x << ' ';
    std::cout << '\n';

    std::cout << "Squeeze Algorithm\n";
    quicksort(ar2, sizeof(ar2)/sizeof(*ar2), squeeze);

    for (auto x : ar2)
        std::cout << x << ' ';
    std::cout << '\n';

    std::cout << "Sweep Algorithm (sorted)\n";
    quicksort(ar, sizeof(ar)/sizeof(*ar), sweep);

    for (auto x : ar)
        std::cout << x << ' ';
    std::cout << '\n';

    std::cout << "Squeeze Algorithm (sorted)\n";
    quicksort(ar2, sizeof(ar2)/sizeof(*ar2), squeeze);

    for (auto x : ar2)
        std::cout << x << ' ';
    std::cout << '\n';
}

输出(随机)

8 28 21 26 10 12 17 1 11 20 30 3 18 5 24 15 9 6 13 27 31 4 16 7 19 22 14 25 29 2 23 
Sweep Algorithm
swap: 8,8
swap: 28,10
swap: 21,12
swap: 26,1
swap: 28,11
swap: 21,3
swap: 17,5
swap: 26,9
swap: 28,6
swap: 20,13
swap: 30,4
swap: 21,7
swap: 18,14
swap: 17,2
swap: 8,8
swap: 10,1
swap: 12,3
swap: 10,5
swap: 11,2
swap: 12,6
swap: 10,4
swap: 11,7
swap: 8,1
swap: 3,3
swap: 5,5
swap: 7,4
swap: 3,3
swap: 4,4
swap: 3,3
swap: 7,7
swap: 13,10
swap: 12,12
swap: 13,13
swap: 12,12
swap: 23,20
swap: 26,16
swap: 28,19
swap: 23,18
swap: 27,17
swap: 20,16
swap: 20,17
swap: 20,18
swap: 16,16
swap: 30,22
swap: 24,24
swap: 30,26
swap: 31,28
swap: 30,27
swap: 26,26
swap: 27,27
swap: 26,26
swap: 30,30
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 
Squeeze Algorithm
swap: 28,2
swap: 21,14
swap: 26,7
swap: 17,4
swap: 20,13
swap: 30,6
swap: 18,9
swap: 14,3
swap: 7,4
swap: 14,9
swap: 12,6
swap: 30,25
swap: 27,21
swap: 31,22
swap: 23,16
swap: 25,17
swap: 30,28
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 
Sweep Algorithm (sorted)
swap: 1,1
swap: 2,2
swap: 3,3
swap: 4,4
swap: 5,5
swap: 6,6
swap: 7,7
swap: 8,8
swap: 9,9
swap: 10,10
swap: 11,11
swap: 12,12
swap: 13,13
swap: 14,14
swap: 15,15
swap: 1,1
swap: 2,2
swap: 3,3
swap: 4,4
swap: 5,5
swap: 6,6
swap: 7,7
swap: 1,1
swap: 2,2
swap: 3,3
swap: 1,1
swap: 5,5
swap: 9,9
swap: 10,10
swap: 11,11
swap: 9,9
swap: 13,13
swap: 17,17
swap: 18,18
swap: 19,19
swap: 20,20
swap: 21,21
swap: 22,22
swap: 23,23
swap: 17,17
swap: 18,18
swap: 19,19
swap: 17,17
swap: 21,21
swap: 25,25
swap: 26,26
swap: 27,27
swap: 25,25
swap: 29,29
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 
Squeeze Algorithm (sorted)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 

祝你好运。

【讨论】:

    【解决方案2】:

    您似乎返回的是枢轴的值,而不是它在分区函数中的位置。

    【讨论】:

    • 编辑为返回“中间”,但分区似乎有问题,它没有正确排序所有数字。任何有助于分区功能的逻辑提示?
    【解决方案3】:

    这是一个使用分区索引的实现:

    void swap(int A[], int i1, int i2){
        int temp=A[i1];
        A[i1]=A[i2];
        A[i2]=temp;
    }
    
    int partition(int A[], int low, int high){
        int partitionIndex, i, pivot;
    
        pivot = A[high];
        partitionIndex=low;
    
        for(i=low; i < high; i++){
            if(A[i]<=pivot){
                swap(A,i,partitionIndex);
                partitionIndex++;
            }
        }
        swap(A,high,partitionIndex);
        return partitionIndex;
    }
    
    void quickSort(int A[], int low, int high) {
        if (low < high){
            int ppos = partition(A, low, high);//does the swapping and returns the pivot
            quickSort(A, low, ppos-1);
            quickSort(A, ppos+1, high);
        }
    }
    

    我无法弄清楚您的代码有什么问题。在调试过程中,我注意到您编辑了问题中的代码。希望上面的代码可以帮助您解决问题。

    【讨论】:

    • 这是一个小修改,因为我返回枢轴的值而不是枢轴点本身。
    • 为什么枢轴开始@高?我们不希望它从中间开始吗?
    • 快速排序的美妙之处在于它会对你的值进行排序,而不管你如何判断是否得到一个支点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-28
    • 2018-09-13
    • 2015-03-31
    • 2021-06-11
    • 2017-08-15
    相关资源
    最近更新 更多