使用中值规则在QuickSort中出错

时间:2015-12-18 06:37:02

标签: java algorithm quicksort

我正在解决Stanford在Algorithms类中的QuickSort赋值,并使用中值规则来选择pivot元素。输入是1-10000的数字,输出是比较的数量

我的功能如下:

public static  int noOfComp = 0;
public static void quick_sort(int[] a, int p, int r){
    if(p<r) {
        noOfComp+= r-p;
        int mid = partition(a, p, r);
        quick_sort(a, p, mid-1); 
        quick_sort(a, mid+1, r);
    }
}

public static int median(int a[],int p, int r){
    int firstPos = p;
    int len = r-p+1;
    int lastPos = r;
    int midPos = len%2==0 ? p + (len)/2-1: p + (len)/2 ;
    int first  = a[firstPos];
    int middle = a[midPos];
    int last   = a[lastPos];

    if (first <= middle) {
      if (middle <= last) {
          // first - middle - last
          return midPos;
      } else if (first <= last) {
          // first - last - middle
          return lastPos;
      }
      // last - first - middle
      return firstPos;
    }

    if (first <= last) {
        // middle - first - last
        return firstPos;
    } else if (middle <= last) {
        // middle - last - first
        return lastPos;
    }
    // last - middle - first
    return midPos;
}


public static int partition(int[] a, int p, int r){
    int chosen = median(a,p,r);
    swap(a, p, chosen);
    int pivot = a[p];
    int i = p;
    for (int j = p+1; j < a.length; j++) {
        if (a[j] < pivot) {
            i++;
            swap(a, i, j);
        }
    }

    swap(a, i,p);
    return i;
}

//main
public static void main(String[] args) throws Throwable{

    int i=0;
    Scanner in = new Scanner(new File("C:\\Users\\Uzumaki Naruto\\Documents\\QuickSort.txt"));
    while(in.hasNext()){
        i++;
        in.next();
    }
    int[] a = new int[i];
    i=0;
    Scanner in2 = new Scanner(new File("C:\\Users\\Uzumaki Naruto\\Documents\\QuickSort.txt"));
    while(in2.hasNext()){
        a[i++] = in2.nextInt();
    }
    quick_sort(a, 0, a.length-1);        
    System.out.println("Number of comparisons : " + noOfComp);
}

问题的答案似乎在128k左右,但我的算法输出132k。我已经阅读了代码次数,但无法确定错误。

1 个答案:

答案 0 :(得分:1)

实际上,我的代码平均计数大约为132k,在随机排列的唯一数字数组上执行。我没有在算法中发现任何错误,除了下面的错误,但它并没有影响你的计数结果,假设正确的代码:

分区中的循环有一个错误的退出条件:

for (int j = p+1; j < a.length; j++) {

应该是:

for (int j = p+1; j <= r; j++) {

以下不是错误,但您可以重写

int len = r-p+1;
int midPos = len%2==0 ? p + (len)/2-1: p + (len)/2 ; 

为:

int midPos = p + (r-p)/2;

但是:你没有计算在函数中位数中进行的比较,通常应该这样做,否则算法无法与另一个(变体)进行公平比较。因此,每次调用 partition 会导致2或3次比较。这将平均数增加到大约148k!

Here它说:

  

使用随机数据透视选择对 n 元素进行排序所需的预期比较数为1.386 n.log(n)三个旋转中位数将其降低到≈1.188 n.log(n)

对于 n = 10 000,1.188 n.log(n)≈158k,所以你的算法似乎比这个估计做的更少比较,至少对于 n 的特殊情况。

我确实看到了再次减少这个数字的方法。

减少比较次数

主要想法是通过将三个检查值中的最低和最高值放在正确的分区中,从函数 median 中进行的比较中获利,因此不需要对它们进行处理进一步由函数 partition 中的循环。

举个例子,如果你有这样的数组:

5, 1, 2, 9, 3

然后中位数将比较5,2和3并选择3作为枢轴值。现在可以将该函数扩展为以正确的顺序放置三个被调查的元素,而无需进行额外的比较,以获得这个:

2, 1, 3*, 9, 5

然后,pivot元素不必交换到数组的开头,而是交换到第二个插槽,因为我们已经确定最左边的元素属于下面的分区:

2, 3*, 1, 0, 5

现在主分区循环可以专注于这个子数组,因为最后一个元素已知属于上层分区:

2, 3*, [1, 0], 5

在循环结束时,最后的交换将使用第二个元素而不是第一个元素:

2, 0, 1, 3*, 5

这将减少主循环中的比较次数为2。

在此变体中, median 函数将在数组中进行几次交换后始终返回第二个插槽的索引:

public static int median(int a[],int p, int r){
    int m = p + (r-p)/2;

    // actually sort the three elements:
    noOfComp++;
    if (a[r] < a[m]) {
        swap(a, r, m);
    }
    if (p < m) { // more than 2 elements
        noOfComp++;
        if (a[m] < a[p]) {
            swap(a, m, p);
            noOfComp++;
            if (a[r] < a[m]) {
                swap(a, r, m);
            }
        }
        // put the middle element (pivot) in second slot
        swap(a, m, p+1);
    }
    return p+1;
}

分区将如下所示:

public static int partition(int[] a, int p, int r){
    int k = median(a, p, r); // always returns p+1 as pivot's index
    int i = k; // (k..i] is lower partition
    for (int j = p+2; j < r; j++) { // positions p and r can be excluded
        if (a[j] < a[k]) {
            i++;
            swap(a, i, j);
        }
    }
    swap(a, i, k); // place pivot between partitions
    return i;
}

quick_sort 中,比较次数将少两次:

    noOfComp += r-p-2;

通过上述调整,比较次数平均从148k降至135k。

所以我担心虽然实际的比较次数已经减少了,但它仍然与128k不匹配。

其他想法

当阵列变小时,我尝试使用insertion sort,但它没有带来太多改进。另一个想法是通过查看更多元素来改进对中位数的搜索,但前提是数组不是太小,因为与分区工作相比,查找数组的成本必须很小。

但是这项任务可能不允许所有这些调整。