问题 QuickSort和Hoare分区


我很难将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 <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;
    }
}

2149
2017-08-25 22:51


起源

我有,编辑原始答案 - Ofek Ron
我想你编辑了组织问题。 - Henk Holterman
检查您的数据样本,您将从12到11个元素(缺少13个元素)。那不可能。 - Henk Holterman
这是根据链接 umiacs.umd.edu/~joseph/classes/enee641/assign5-solution.pdf - Ofek Ron
只是为了澄清,什么是“CLRS,第二版”?请提供该书的标准引文或链接。 - Al Kepp


答案:


回答“为什么Hoare分区工作?”的问题:

让我们将数组中的值简化为三种: 大号 值(小于枢轴值的那些), Ë 值(等于枢轴值),和 G 值(大于枢轴值的值)。

我们还将为数组中的一个位置指定一个特殊名称;我们称这个位置 小号,它是的地方 Ĵ 指针是程序结束的时候。我们提前知道哪个位置 小号 是什么?不,但我们知道 一些 位置将符合该描述。

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

  1. “低”子阵列,从阵列的左端到并包括 小号,包含没有 G 值。
  2. “高”子阵列,紧接着之后开始 小号 并继续到右端,包含否 大号 值。

这才是我们真正需要做的。我们甚至不用担心在哪里 Ë 价值随着任何给定的传递而结束。只要每个传递使子阵列相对于彼此正确,后来的传递将处理任何子阵列内存在的任何障碍。

所以现在让我们从另一方面解决问题:分区过程如何确保没有 G 价值观 小号 或者在它的左边,没有 大号 价值在右边 小号

好吧,“右边的价值观 小号“与”细胞集合相同“ Ĵ 指针在到达之前移动 小号“。并且”左边和包括的一组价值观 小号“与”的价值观相同 一世 指针移动之前 Ĵ 到达 小号”。

这意味着任何价值观  在循环的某些迭代中,错位的将在我们的两个指针中的一个之下。 (为方便起见,让我们说它是 Ĵ 指针指向 大号 价值,虽然它的工作方式完全相同 一世 指针指向 G 价值。) 一世 指针是什么时候的 Ĵ 指针位于错位值?我们知道它将是:

  1. 在“低”子阵列中的某个位置 大号 价值可以毫无问题;
  2. 指着一个值是一个 Ë 或者a G 价值,可以很容易地取代 大号 价值在 Ĵ 指针。 (如果不是 Ë 或者a G 价值,它不会停在那里。)

请注意,有时候 一世 和 Ĵ 指针实际上都会停止 Ë 值。发生这种情况时,即使不需要,也会切换值。但这并没有造成任何伤害;我们之前说的是放置了 Ë 值不能导致子数组之间的错误排序。

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

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

6
2017-07-26 15:34



当j指针位于错位值时,i指针会在哪里?我们知道它会是:我不可能越过j吗? - codey modey
“我不可能越过j吗?”是;在某些时候,i和j将交叉,或者它们将停在相同的值上。但是当这两件事中的任何一件发生时,条件(i <j)失败并且过程返回值j。 - afeldspar


我相信这段代码存在两个问题。首先,在你的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版本中,这里没有提到;我不得不去 这本书的勘误页面 找到这个。这几乎可以肯定是“访问超出范围”错误所遇到的问题的原因,因为在不变的情况下,您可能会立即从阵列中运行!

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

希望这可以帮助!


5
2017-08-25 23:05



首先,谢谢,这确实有帮助其次我仍然得到一个错误:运行时检查失败#2 - 变量'a'周围的堆栈已损坏。 - Ofek Ron
没有 j=r+1 事情。从调用模式可以看出,这使用了真正的C语言 [inclusiveStart, ExclusiveEnd) 惯例。 - Henk Holterman
这解释了运行时错误,一些奇怪的问题 - 只是j它停止了排序一步b4完成它,并且用j + 1它对数组进行排序但超出范围......出了什么问题? - Ofek Ron
@Henk Holterman-你确定吗?我在我的机器上对此进行了测试,看起来他似乎正在使用包容性端点;如果你提供一个包含十个元素的数组并指定范围[0,9],它会正确地对它进行排序。 - templatetypedef
@Ofek Ron-随着更改,代码正在我的机器上运行。如何指定要排序的范围的端点? - templatetypedef


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

实际上,HoarePartition适用于任何阵列 A[p...r] 它包含至少2个元素(即 p < r),每一个元素 A[p...j] 是 <= 每个元素 A[j+1...r] 当它终止。 所以主要算法重复出现的下两个段是 [start...q] 和 [q+1...end]

所以正确的C代码如下:

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;
    }
}

更多说明:

  1. 分区部分只是伪代码的翻译。 (注意返回值是 j

  2. 对于递归部分,请注意基本情况检查(end <= start 代替 end <= start + 1 否则你会跳过 [2 1] 子阵列)


3
2018-05-23 16:38





你最后的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。 所以我们必须交换以保证分区的功能。


0
2017-10-18 14:08





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

0
2018-01-14 14:52





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

我会解释你将最左边的元素视为枢轴。

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

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

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

i=p-1,

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

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

因此,我们将移动'i'标记,直到我们得到大于或等于枢轴的元素。类似'j'标记,直到我们发现元素小于或等于pivot。

现在如果我<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]);

现在如果'i'不小于'j',那意味着现在交换中没有元素,所以我们返回'j'位置。

所以现在分区后半部分的数组是从'start to j'

上半部分是从'j + 1到结尾'

所以代码看起来像

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);
}

0
2018-02-20 06:44





在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;
    }
}

0
2018-04-09 08:48