QuickSort和Hoare分区

时间:2011-08-25 22:51:26

标签: c algorithm sorting quicksort data-partitioning

我很难将QuickSort与Hoare分区转换为C代码,但无法找到原因。我正在使用的代码如下所示:

void QuickSort(int a[],int start,int end) {
    int q=HoarePartition(a,start,end);
    if (end<=start) return;
    QuickSort(a,q+1,end);
    QuickSort(a,start,q);
}

int HoarePartition (int a[],int p, int r) {
    int x=a[p],i=p-1,j=r;
    while (1) {
        do  j--; while (a[j] > x);
        do  i++; while (a[i] < x);

        if  (i < j)
            swap(&a[i],&a[j]);
        else
            return j;
    }
}

另外,我真的不明白为什么HoarePartition有效。有人可以解释它为什么有用,或者至少把我链接到一篇文章吗?

我已经看到了分区算法的逐步完成,但我没有直观的感觉。在我的代码中,它似乎甚至没有用。例如,给定数组

13 19  9  5 12  8  7  4 11  2  6 21

它将使用数据透视表13,但最终会使用数组

 6  2  9  5 12  8  7  4 11 19 13 21 

并将返回j a[j] = 11。我认为从那个点开始并且前进的数组应该具有比枢轴更大的值,这应该是真的,但是这不是真的,因为11&lt; 13。

这是Hoare分区的伪代码(来自CLRS,第二版),如果这很有用:

Hoare-Partition (A, p, r)
    x ← A[p]
    i ← p − 1
    j ← r + 1
    while  TRUE
        repeat   j ←  j − 1
            until     A[j] ≤ x
        repeat   i ←  i + 1
            until     A[i] ≥ x
        if  i < j
            exchange  A[i] ↔ A[j]
        else  return   j 

谢谢!

编辑:

此问题的正确C代码将最终成为:

void QuickSort(int a[],int start,int end) {
    int q;
    if (end-start<2) return;
    q=HoarePartition(a,start,end);
    QuickSort(a,start,q);
    QuickSort(a,q,end);
}

int HoarePartition (int a[],int p, int r) {
    int x=a[p],i=p-1,j=r;
    while (1) {
        do  j--; while (a[j] > x);
        do  i++; while (a[i] < x);
        if  (i < j) 
            swap(&a[i],&a[j]);
        else 
            return j+1;
    }
}

7 个答案:

答案 0 :(得分:7)

回答&#34;为什么Hoare分区有效?&#34;:

让我们将数组中的值简化为三种: L 值(小于透视值的值), E 值(等于枢轴值)和 G 值(大于枢轴值的值)。

我们还会为数组中的一个位置指定一个特殊名称;我们将此位置称为 s ,它是程序完成时 j 指针所在的位置。我们提前知道 s 的位置是什么?不,但我们知道某些位置符合该说明。

使用这些术语,我们可以用稍微不同的术语表达分区过程的目标:它是将单个数组拆分为两个较小的子数组,这些子数组未错误排序相对于彼此。那&#34;没有错误排序&#34;如果满足以下条件,则满足要求:

  1. &#34; low&#34;子阵列,从阵列的左端到 s ,不包含 G 值。
  2. &#34; high&#34;子阵列,在 s 之后立即启动并继续到右端,不包含 L 值。
  3. 我们真的需要做的就是这些。我们甚至不用担心 E 值会在任何给定的传球中结束。只要每个传递使子阵列相对于彼此正确,后来的传递将处理任何子阵列中存在的任何障碍。

    现在让我们从另一方面解决问题:分区程序如何确保 s 或左侧没有 G 值它, s 右边没有 L 值?

    嗯,&#34; s &#34;右边的一组值与&#34; j 指针在到达 s &#34;之前移动的单元格集相同。并且&#34;左边的值集合,包括 s &#34;与&#34; i 指针在 j 到达 s &#34;之前移动的值集合相同。

    这意味着 错位的任何值都会在循环的某个迭代中位于我们的两个指针之一。 (为方便起见,让我们说它是指向 L 值的 j 指针,尽管它与 i完全相同指针指向 G 值。)当 j 指针位于错位值时, i 指针在哪里?我们知道它会:

    1. 位于&#34; low&#34;子阵列,其中 L 值可以没有问题;
    2. 指向一个 E G 值的值,可以轻松替换 L 下的 L j 指针。 (如果它不在 E G 值上,它就不会停在那里。)
    3. 请注意,有时 i j 指针实际上都会停在 E 值上。发生这种情况时,即使不需要,也会切换值。但是,这并没有造成任何伤害;我们之前说过, E 值的放置不会导致子阵列之间的错误排序。

      因此,总而言之,Hoare分区的工作原因是:

      1. 它将数组分成较小的子数组,这些子数组相对于彼此没有错误排序;
      2. 如果你继续这样做并递归地对子阵列进行排序,那么最终阵列中没有任何东西会被排除。

答案 1 :(得分:5)

我认为此代码存在两个问题。首先,在Quicksort函数中,我认为你想重新排序行

 int q=HoarePartition(a,start,end);
 if (end<=start) return;

这样你就可以这样:

 if (end<=start) return;
 int q=HoarePartition(a,start,end);

然而,你应该做的比这更多;特别应该阅读

 if (end - start < 2) return;
 int q=HoarePartition(a,start,end);

原因是如果您尝试分区的范围大小为零或一,则Hoare分区无法正常工作。在我的CLRS版本中,这里没有提到;我不得不去 the book's errata page 找到这个。这几乎可以肯定是“访问超出范围”错误所遇到的问题的原因,因为在不变的情况下,你可以直接从数组中运行!

至于Hoare分区的分析,我建议首先手动追踪它。还有一个更详细的分析 here 。直观地说,它通过从范围的两端向另一端增长两个范围来工作 - 一个在左侧包含小于枢轴的元素,一个在右侧包含比枢轴大的元素。这可以稍加修改,以生成Bentley-McIlroy分区算法(在链接中引用),该算法可以很好地扩展以处理相等的密钥。

希望这有帮助!

答案 2 :(得分:3)

您的最终代码错误,因为j的初始值应为r + 1而不是r。否则,您的分区函数始终忽略最后一个值。

实际上,HoarePartition是有效的,因为对于包含至少2个元素(即A[p...r])的任何数组p < rA[p...j]的每个元素都是<= A[j+1...r]的每个元素[start...q] 1}}当它终止时 因此,主算法重复出现的下两个段是[q+1...end]void QuickSort(int a[],int start,int end) { if (end <= start) return; int q=HoarePartition(a,start,end); QuickSort(a,start,q); QuickSort(a,q + 1,end); } int HoarePartition (int a[],int p, int r) { int x=a[p],i=p-1,j=r+1; while (1) { do j--; while (a[j] > x); do i++; while (a[i] < x); if (i < j) swap(&a[i],&a[j]); else return j; } }

所以正确的C代码如下:

j

更多说明:

  1. 分区部分只是伪代码的翻译。 (注意返回值为end <= start

  2. 对于递归部分,请注意基本情况检查(end <= start + 1而不是[2 1],否则您将跳过 {% for f in resultat %} <div class="box"> <BR> <div class="row uniform 50%"> <div class="6u 12u(mobilep)"> {{ f.typeposte }} </div> </div> <BR> <div class="row uniform 50%"> <div class="6u 12u(mobilep)"> {{ f.diplome }} </div> </div> <BR> <div class="row uniform 50%"> <div class="6u 12u(mobilep)"> {{ f.niveau }} </div> </div> <BR> <div class="row uniform 50%"> <div class="6u 12u(mobilep)"> {{ f.duree }} </div> </div> <BR> <div class="row uniform 50%"> <div class="6u 12u(mobilep)"> {{ f.commentaire }} </div> </div> <div class="box"> <form class="form_app" action="/apply" method="post"> {% csrf_token %} <div class="row uniform 50%"> <div class="6u 12u(mobilep)"> {{form_app.apply}} </div> <input type="hidden" name="title" value="ouf"> </div> <div class="row uniform"> <div class="12u"> <ul class="actions align-center"> <li><input type="submit" value="OK"/></li> </ul> </div> </div> </form> </div> 子阵列

答案 3 :(得分:0)

你最后的C代码是有效的。但这并不直观。 现在我幸运地正在学习CLRS。 在我看来,CLRS的伪代码是错误的。(在2e) 最后,我发现改变一个地方是正确的。

 Hoare-Partition (A, p, r)
 x ← A[p]
     i ← p − 1
     j ← r + 1
 while  TRUE
        repeat   j ←  j − 1
            until     A[j] ≤ x
    repeat   i ←  i + 1
            until     A[i] ≥ x
    if  i < j
              exchange  A[i] ↔ A[j]
    else  
              exchnage  A[r] ↔ A[i]  
              return   i

是的,添加交换A [r]↔A [i]可以使其有效。 为什么? 因为A [i]现在大于A [r] OR i == r。 所以我们必须交换以保证分区的功能。

答案 4 :(得分:0)

  1. 将枢轴移至第一位。 (例如,使用三个中值。切换到小输入大小的插入排序。)
  2. 分区,
    • 重复交换当前最左边的1与当前最右边的0.
      0 - cmp(val,pivot)== true,1 - cmp(val,pivot)== false 如果不离开则停止&lt;右。
    • 之后,用最右边的0交换枢轴。

答案 5 :(得分:0)

首先,你误解了Hoare的分区算法,可以从c中的翻译代码看出, 因为你认为枢轴是子阵列最左边的元素。

我会解释你把最左边的元素当作支点。

int HoarePartition (int a[],int p, int r) 

这里p和r表示数组的下限和上限,也可以是要分区的较大数组(子数组)的一部分。

所以我们从最初指向数组终点之前和之后的指针(marker)开始(简单地说是使用do while循环的bcoz)。因此,

i=p-1,

j=r+1;    //here u made mistake

现在按照分区我们希望pivot的左边的每个元素都小于或等于pivot,大于pivot的右边。

所以我们会移动&#39;我&#39;标记直到我们得到的元素是大于或等于枢轴。同样地,&#39; j&#39;标记直到我们发现元素小于或等于pivot。

现在,如果我&lt; j我们交换元素bcoz这两个元素都在数组的错误部分。所以代码将是

do  j--; while (a[j] <= x);                 //look at inequality sign
do  i++; while (a[i] >= x);
if  (i < j) 
    swap(&a[i],&a[j]);

现在,如果&#39;我&#39;不小于&#39; j&#39;,这意味着现在交换之间没有任何元素,所以我们返回&#39; j&#39;位置。

所以现在分割下半部分后的数组来自&#39;开始到j&#39;

上半部分是从&#39; j + 1到结束&#39;

所以代码看起来像

void QuickSort(int a[],int start,int end) {
    int q=HoarePartition(a,start,end);
    if (end<=start) return;
    QuickSort(a,start,q);
    QuickSort(a,q+1,end);
}

答案 6 :(得分:0)

在java中直接实现。

public class QuickSortWithHoarePartition {

    public static void sort(int[] array) {
        sortHelper(array, 0, array.length - 1);
    }

    private static void sortHelper(int[] array, int p, int r) {
        if (p < r) {
            int q = doHoarePartitioning(array, p, r);
            sortHelper(array, p, q);
            sortHelper(array, q + 1, r);
        }
    }

    private static int doHoarePartitioning(int[] array, int p, int r) {
        int pivot = array[p];
        int i = p - 1;
        int j = r + 1;

        while (true) {

            do {
                i++;
            }
            while (array[i] < pivot);

            do {
                j--;
            }
            while (array[j] > pivot);

            if (i < j) {
                swap(array, i, j);
            } else {
                return j;
            }
        }

    }

    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}