为什么这个Quicksort有效?

时间:2010-05-21 13:21:26

标签: c algorithm quicksort

我发现这种Quicksort分区方法令人困惑和错误,但似乎有效。我指的是this pseudocode注意:他们在文章末尾也有一个C实现,但它与他们的伪代码非常不同,所以我不关心。

我也用C这样写它,试图尽可能地保持伪代码,即使这意味着做一些奇怪的C东西:

#include <stdio.h>

int partition(int a[], int p, int r)
{
    int x = a[p];

    int i = p - 1;
    int j = r + 1;

    while (1)
    {
        do
            j = j - 1;
        while (!(a[j] <= x));

        do
             i = i + 1;
        while (!(a[i] >= x));

        if (i < j)
        {
            int t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
        else
        {
            for (i = 1; i <= a[0]; ++i)
                printf("%d ", a[i]);
            printf("- %d\n", j);

            return j;
        }
    }
}


int main()
{
    int a[100] = //{8, 6,10,13,15,8,3,2,12};
                {7, 7, 6, 2, 3, 8, 4, 1};

    partition(a, 1, a[0]);
    return 0;
}

如果你运行它,你将得到以下输出:

  

1 6 2 3 4 8 7 - 5

但是,这是错的,不是吗?很明显,a[5]a[2] = 6 > a[5] = 4以来没有低于它的所有值。更不用说7应该是枢轴(最初的a[p]),但它的位置既错误又丢失。

以下分区算法取自wikipedia

int partition2(int a[], int p, int r)
{
    int x = a[r];
    int store = p;

    for (int i = p; i < r; ++i)
    {
        if (a[i] <= x)
        {
            int t = a[i];
            a[i] = a[store];
            a[store] = t;

            ++store;
        }
    }

    int t = a[r];
    a[r] = a[store];
    a[store] = t;

    for (int i = 1; i <= a[0]; ++i)
        printf("%d ", a[i]);
    printf("- %d\n", store);

    return store;
}

并产生此输出:

  

1 6 2 3 8 4 7 - 1

在我看来,这是正确的结果:枢轴(a[r] = a[7])已达到最终位置。

但是,如果我在以下算法中使用初始分区功能:

void Quicksort(int a[], int p, int r)
{
    if (p < r)
    {
        int q = partition(a, p, r); // initial partitioning function

        Quicksort(a, p, q);
        Quicksort(a, q + 1, r); // I'm pretty sure q + r was a typo, it doesn't work with q + r.
    }
}

......它似乎是一种正确的排序算法。我在很多随机输入上测试了它,包括长度为20的所有0-1数组。

我还尝试将此分区函数用于选择算法,但无法生成正确的结果。它似乎工作,但它甚至非常快,作为快速排序算法的一部分。

所以我的问题是:

  1. 任何人都可以发布算法不起作用的示例吗?
  2. 如果没有,为什么它有效,因为分区部分似乎是错误的?这是另一种我不知道的分区方法吗?

4 个答案:

答案 0 :(得分:4)

我认为分区是正确的。 7是枢轴。原始数组被分区为长度为5的子数组,其中包含小于或等于7的元素和一个长度为2的子数组,其中包含大于或等于7的元素。

答案 1 :(得分:1)

从上面展开它应该是什么样子

void swap(int *a, int *b)
{
    int x;

    x = *a;
    *a = *b;
    *b = x;
}

int partition(int s[], int l, int h) 
{ 
    int i;
    int p;/* pivot element index */ 
    int firsthigh;/* divider position for pivot element */ 

    p = h; 
    firsthigh = l; 
    for (i = l; i < h; i++) 
        if(s[i] < s[p]) { 
            swap(&s[i], &s[firsthigh]); 
            firsthigh++; 
        } 
    swap(&s[p], &s[firsthigh]); 

    return(firsthigh); 
}

void quicksort(int s[], int l, int h)
{
    int p;/* index of partition */ 
    if ((h - l) > 0) {
        p = partition(s, l, h); 
        quicksort(s, l, p - 1); 
        quicksort(s, p + 1, h); 
    } 
}

int main()     
{         
    int a[100] = //{8, 6,10,13,15,8,3,2,12};  
                   {7, 7, 6, 2, 3, 8, 4, 1};              
    quicksort(a, 0, 7);
    return 0; 
}    

答案 2 :(得分:0)

来自Wikipedia(我已经强调了我认为直接解决你问题的部分):

  

这是就地分区   算法。它划分了部分   索引左和之间的数组   通过移动所有权利,包容性   小于或等于的元素   array [pivotIndex]到开头   这个子阵列,让所有更大的   跟随他们的元素。在里面   过程它也找到了最后的结果   枢轴元素的位置,哪个   它返回。 暂时移动了   枢轴元素到最后   子阵列,所以它没有进入   方式。因为它只使用   交流,最终名单也一样   元素作为原始列表。注意   元素可以交换   到达它之前多次   最后的地方。还应该指出   在枢轴重复的情况下   输入数组,它们可以传播   横跨左边的子阵列,可能在   随机顺序。这并不代表一个   分区失败,进一步   排序将重新定位,最后   将它们“胶”在一起。

这可能是你错过的吗?

答案 3 :(得分:0)

您对项目的索引和iten值

感到困惑

查看标题

int partition(int a[], int p, int r) ;

现在,如果我们将数组a上的数据类型更改为某种奇怪的数据类型,您将看到问题

int partition( Otherdatatype a[], int p, int r) ;

您可以使用

从主要内部调用该功能
partition(a, 1, a[0]);

见问题a [0]是[0]中条目的值而不是索引值。

想象一下,[0]代码中的值为200只是将第一项值更改为200,并且您将获得运行时错误“尝试访问超出范围的内存”,因为如果您关注 通过[0] = 200传递到分区作为值r然后跟随分区内发生的事情。

要记住的是,这是分区标题中的排序例程,数组a中的列表可能与索引的类型不同。标题的p和r显然是指向索引位置的索引和是要排序的列表。

因此,你的主要开始是

partition(a, 0, items_in_array-1);

你明白为什么吗?数组a从[0] ... a [items_in_array-1]

运行

因此,在上面的示例中,您已将8个值预先加载到数组中,因此您对main的分区调用应为

partition(a, 0, 7);