【问题标题】:Stack overflow error with Quicksort and median implementation快速排序和中值实现的堆栈溢出错误
【发布时间】:2015-12-02 10:20:50
【问题描述】:

首先我要声明这是一个我已经进行了大量尝试的家庭作业问题。

有人要求我修改 Java 中的快速排序,以使用公式 i * (n-1) /8 将枢轴设置为数组中 9 个值的伪中位数

我写了一个computeMedian 方法,它接受 3 个整数,确定最大值,然后返回那个值。

代码:

public static int computeMedian(int x, int y, int z)
    {
        if((x >= y && x <= z) || (x >= z && x <= y)) {return x;}
        else if((y >= x && y <= z) || (y >= z && y <= x)) {return y;}
        else if((z >= x && z <= y) || (z >= y && z <= x)) {return z;}
        else { return 0; }
    }

然后我在我的 findPivot 方法中使用它,该方法采用当前的 array, from, to 值并使用它们来构造一个枢轴

代码如下:

public static int findPivot(int[] a, int from, int to)
    {
        if(a.length <= 7)
        {
            return a[(to)/2];
        }
        else if(a.length > 7 && a.length <= 40)
        {
            return computeMedian(a[from], a[(to)/2] , a[to]);
        }
        else
        {
            int x = computeMedian(a[0 * (to) / 8], a[1 * (to) / 8], a[2 * (to) / 8]);
            int y = computeMedian(a[3 * (to) / 8], a[4 * (to) / 8], a[5 * (to) / 8]);
            int z = computeMedian(a[6 * (to) / 8], a[7 * (to) / 8], a[8 * (to) / 8]);
            return computeMedian(x,y,z);
        }
    }

此方法适用于对任何小于或等于 40 的数组进行排序,但一旦它大于 40,我就会收到一个堆栈溢出错误,导致我在 else {} 部分返回我的 computeMedian 方法。我会注意到 return computeMedian(a[from], a[(to)/2] , a[to]); 如果我把它放在 > 40 部分,那它只是 3 个值的中值,而不是 3 组中值的中值。

目前这是我将findPivot 插入快速排序分区方法的方式:

private static int modPartition(int[] a, int from, int to)
    {
        int pivot = findPivot(a, from, to);
        int i = from - 1;
        int j = to + 1;
        while(i < j)
        {
            i++; while (a[i] < pivot) { i++; }
            j--; while (a[j] > pivot) { j--; }
            if (i < j) { swap(a, i, j); }
        }
        return j;
    }

我非常困惑为什么我的 computeMedian 方法无法处理更大的数据集。我尝试通过 for 循环将 i * (n-1) / 8 值放入数组中,对它们进行排序并在中间返回值,并将值放入数组 p 并调用 computeMedian(computeMedian(p[0], p[1], p[2]), computeMedian(p[3],p[4],p[5]),...etc,我得到相同的堆栈溢出问题,但它往往会移动到我代码的不同部分并引导我转圈。

如果有人需要,我可以发布更多的 sn-ps,但我认为我的问题可能就在这里。

感谢您的帮助。我还在学习,我认为掌握这一点完全可以帮助我在未来自己解决问题。

以下是堆栈跟踪中的问题行: 第 16 行:int p = modPartition(a, from, to); 18号线modSort(a, p+1, to); 23号线int pivot = findPivot(a, from, to);

这也是我的整个 modSort 方法:

public static void modSort(int[]a, int from, int to)
    {
        if(from >= to) { return; }
        int p = modPartition(a, from, to);
        modSort(a, from, p);
        modSort(a, p+1, to);
    }

【问题讨论】:

  • 对不起,我没有阅读您的所有文字,但据我所知,您添加的代码都没有执行递归调用,因此它们不会导致堆栈溢出。发生错误时的错误信息和堆栈跟踪是什么?
  • @Andreas Bluej 告诉我 java.lang.*Error: null stack trace: java.lang.*Error at BMQuicksorter.modPartition(BMQuicksorter.java:23) at BMQuicksorter.modSort(BMQuicksorter.java:16) at BMQuicksorter.modSort(BMQuicksorter.java:18) at BMQuicksorter.modSort(BMQuicksorter.java:18) at BMQuicksorter.modSort(BMQuicksorter.java:18) 我将在原始帖子中添加哪些行
  • 尝试为特定异常设置断点。
  • @Javier 很抱歉,但不幸的是我不完全明白你的意思。你能详细说明一下吗?谢谢
  • 我看不到无限递归发生在哪里 - 你确定你正在执行你发布的代码吗?即您是否可能正在运行以前编译的代码,而不是您正在编辑的编译版本?

标签: java sorting quicksort median-of-medians


【解决方案1】:

转载和修正

添加代码以重现错误...

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

public static void main(String[] args) {
    // Generate a sample
//      ArrayList<Integer> list = new ArrayList<>(64);
//      for (int i = 0; i < 64; i++) list.add(i);
//      Collections.shuffle(list);
//      System.out.println(list);
    int[] arr = {40, 9, 2, 62, 8, 42, 46, 23, 61, 45, 63, 48, 43, 36, 33, 32, 1, 55, 7, 17, 16, 25, 5, 26, 22, 11, 56, 38, 60, 31, 58, 29, 51, 34, 24, 54, 4, 3, 30, 20, 57, 18, 50, 44, 41, 12, 59, 6, 53, 39, 37, 35, 28, 13, 14, 15, 0, 19, 49, 52, 21, 27, 47, 10};

    modSort(arr, 0, arr.length-1);

    System.out.println(Arrays.toString(arr));
}

调试。为*Error 设置断点(如 cmets 中所建议的)不起作用。所以我在行(modSort 的开头)处设置一个常规断点。

对于这个示例数据开始对modSortfrom=3;to=5 进行无限递归。对于该范围,枢轴 p = 2,这似乎是不正常的。

我责怪findPivot(a,from,to) 方法。看起来很适合为整个 a 找到一个支点,但不适用于一个范围。尝试这个更正:

public static int findPivot(int[] a, int from, int to) {
    final int rangeLength = to - from + 1;
    if(rangeLength <= 7) {
        return a[(from + to)/2];
    } else if(rangeLength  <= 40) { // why test "a.length > 7" ?
        return computeMedian(a[from], a[(from + to)/2] , a[to]);
    } else {
        final int rangeLength_8 = (to - from) / 8;
        int x = computeMedian(a[from], a[from + rangeLength_8], a[from + 2 * rangeLength_8]);
        int y = computeMedian(a[from + 3 * rangeLength_8], a[from + 4 * rangeLength_8], a[from + 5 * rangeLength_8]);
        int z = computeMedian(a[from + 6 * rangeLength_8], a[from + 7 * rangeLength_8], a[to]);
        return computeMedian(x,y,z);
    }
}

然后它适用于我的示例。我在这一点上停止它(必须睡一觉)。

我认为您应该尝试熟悉调试器。我想你应该更容易弄清楚。

【讨论】:

  • 您对我的帮助超出了您的想象。
【解决方案2】:

现在您实际上已经包含了堆栈溢出问题的代码和错误消息,我们可以为您提供帮助。

从您的堆栈跟踪中,我们可以看到无限递归可能是对modSort 的第二次调用,因为第 18 行重复了。

由于该调用和传入参数之间的唯一区别是第二个参数,我敢打赌p 小于from

确认这一点的最佳方法是插入一个好的老式print 语句。

public static void modSort(int[]a, int from, int to)
{
    if(from >= to) { return; }
    int p = modPartition(a, from, to);
    System.out.println("from=" + from + ", to=" + to + ", p=" + p);
    modSort(a, from, p);
    modSort(a, p+1, to);
}

生成的输出应该非常清楚地显示出问题所在。

【讨论】: