Quicksort - Hoare的重复值分区

时间:2016-11-01 20:58:45

标签: algorithm sorting quicksort partitioning

我已经为Quicksort实现了经典的Hoare分区算法。它适用于任何唯一数字列表[3,5,231,43]。唯一的问题是当我有一个重复的列表[1,57,1,34]。如果我得到重复的值,我进入一个无限循环。

private void quicksort(int[]a, int lo, int hi) {
    if (lo < hi) {
        int q = hoare_partition(a, lo, hi);
        quicksort(a, lo, q - 1);
        quicksort(a, q + 1, hi);
    }
}

private int hoare_partition(int[] a, int lo, int hi) {

    int pivot = a[hi];
    int i = lo;
    int j = hi;

    while (true) {

        while (a[i] < pivot) i++;
        while (a[j] > pivot) j--;

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

Hoare的实施是否可能不正确且无法应对重复?

我知道这已被多次询问(我尝试了所有这些)但我很难找到这个实现的解决方案。

4 个答案:

答案 0 :(得分:6)

algorithm partition(A, lo, hi) is
    pivot := A[lo]
    i := lo – 1
    j := hi + 1
    loop forever
        do
            i := i + 1
        while A[i] < pivot

        do
            j := j – 1
        while A[j] > pivot

        if i >= j then
            return j

        swap A[i] with A[j]

上面的伪代码取自Wikipedia。我们将它与您的代码进行比较。

问题是你必须在交换后移动索引。伪代码使用do-while循环而不是while循环来在交换后移动索引。另请注意ij的初始值。

algorithm quicksort(A, lo, hi) is
    if lo < hi then
        p := partition(A, lo, hi)
        quicksort(A, lo, p)
        quicksort(A, p + 1, hi)

对于递归步骤,您可能需要处理边缘情况(即索引)。如果您将quicksort(a, lo, q-1)更改为quicksort(a, lo, q),则应该可以使用。

我刚写的一个完整的工作版本:

import java.util.Arrays;

public class Test {

    public static void main(String[] args) throws Exception {
        int[] input = {1, 57, 1, 34};
        quicksort(input, 0, input.length - 1);
        System.out.println(Arrays.toString(input));
    }

    private static void quicksort(int[]a, int lo, int hi) {
        if (lo < hi) {
            int q = hoare_partition(a, lo, hi);
            quicksort(a, lo, q);
            quicksort(a, q + 1, hi);
        }
    }

    private static int hoare_partition(int[] a, int lo, int hi) {

        int pivot = a[lo];
        int i = lo - 1;
        int j = hi + 1;

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

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

            if (i >= j) {
                return j;
            }
            swap(a, i, j);

        }
    }

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

}

如果您更喜欢while循环而不是do-while

private static int hoare_partition(int[] a, int lo, int hi) {

            int pivot = a[lo];
            int i = lo;
            int j = hi;

            while (true) {

                while (a[i] < pivot) i++;

                while (a[j] > pivot) j--;

                if (i >= j) {
                    return j;
                }
                swap(a, i, j);
                i++;
                j--;

            }
        }

答案 1 :(得分:1)

这是一个C ++示例,它实现了Hoare方案加上3个检查的中位数,一个重复的数据检查(以排除中间值),只在较小的部分使用递归,循环返回较大的部分(这可以防止堆栈溢出) )。最坏的情况时间复杂度仍为O(n ^ 2),但需要相当具体的数据模式才能产生这种情况(3的中位数必须始终保持接近最小值或接近最大值)。

void QuickSort(uint32_t a[], size_t lo, size_t hi) {
    while(lo < hi){
        size_t i = lo, j = (lo+hi)/2, k = hi;
        uint32_t p;
        if (a[k] < a[i])            // median of 3
            std::swap(a[k], a[i]);
        if (a[j] < a[i])
            std::swap(a[j], a[i]);
        if (a[k] < a[j])
            std::swap(a[k], a[j]);
        p = a[j];
        i--;                        // Hoare partition
        k++;
        while (1) {
            while (a[++i] < p);
            while (a[--k] > p);
            if (i >= k)
                break;
            std::swap(a[i], a[k]);
        }
        i = k++;
        while(i > lo && a[i] == p)  // exclude middle values == pivot
            i--;
        while(k < hi && a[k] == p)
            k++;
        // recurse on smaller part, loop on larger part
        if((i - lo) <= (hi - k)){
            QuickSort(a, lo, i);
            lo = k;
        } else {
            QuickSort(a, k, hi);
            hi = i;
        }
    }
}

答案 2 :(得分:1)

上面@Terry Li的答案的地址,因为我的声誉不足以发表评论。首先,非常感谢您提供的详细信息。但是,我相信您提供的while循环的替代功能存在一些问题。它不返回原始数据透视表的索引,并且本身不准确。 (请尝试使用hoare_partition([6,7,8,9,1,2,3,4,5], 0, 8))此问题是由于同时增加i和减少j引起的,从而使您失去了对枢轴的跟踪。因此,在下面提出的修订中,我插入了一个小条件以确保存储枢轴的任何索引都不会更改。 如果我错了,请纠正我。

private static int hoare_partition(int[] a, int lo, int hi) {

            int pivot = a[lo];
            int i = lo;
            int j = hi;

            while (true) {

                while (a[i] < pivot) i++;

                while (a[j] > pivot) j--;

                if (i >= j) {
                    return i;
                }
                swap(a, i, j);
                if (a[j] == pivot)
                    i++;
                elif (a[i] == pivot)
                    j--;

            }
        }

答案 3 :(得分:0)

以下是while循环的另一个例子

private static int partition(int[] a, int start, int end){
   int i = start-1, j = end+1;

   while(true){
      while(a[++i] < a[start]);
      while(a[start] < a[--j]);

      if (i >= j) return j;
      swap(a, i, j);
   }
}