【问题标题】:Quicksort Algorithm not assigning pivot correctly快速排序算法未正确分配枢轴
【发布时间】:2013-02-24 07:01:23
【问题描述】:

我观看了快速排序算法的精彩可视化:http://www.youtube.com/watch?v=Z5nSXTnD1I4

我觉得我真的理解了快速排序背后的原理,并在一些在线指南的帮助下,着手创建自己的快速排序。
这是我想出的:

public void quickSort(int[] a, int left, int right) {

    int index = partition(a, left, right);
    if (left < index - 1)
      quickSort(a, left, index);
    if (index < right)
      quickSort(a, index + 1, right);
}

private int partition (int[] a, int left, int right) {
    int i = left - 1;
    int j = right + 1;
    int pivot = a[0];

    while (i < j) {

        i++;

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

        j--;

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

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

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

left和right的值如下:

left = 0
right = array size - 1

很遗憾,输出不正确。问题似乎在于我对支点的处理。在我观看的可视化中,讲师物理地移除了枢轴并让指针指向任何东西。他继续教程,当他到达 i 和 j(我称之为左和右)都指向同一个空白点时,他插入枢轴并继续。

由于我在物理上将枢轴固定在适当的位置,因此我发现很难对其进行正确排序。

在这段代码中,我正在处理输入:

4 8 1 6 3 7 2 5

我得到了输出:

1 3 2 6 8 7 4 5

一旦“4”值(即枢轴)在算法的一开始就被排序,我从不使用它,这会把所有东西都扔掉。另外,我认为 quickSort 方法有问题。

有人能给我一些建议吗?谢谢。

编辑:此处的两个编辑已被删除,因为它们包含不必要和不正确的信息。其中一个将枢轴更改为:(左+右)/ 2。由于下面的答案中解释的原因,这当然是错误的。

【问题讨论】:

  • 关闭过于本地化。
  • @djechlin:根据常见问题解答,我认为它针对特定的编程算法。如果我错了,你能推荐在哪个 Stack Exchange 上问这个问题吗?
  • @djechlin 他在问一个显示代码的编码问题。这怎么可能太本地化了??
  • @djechlin:我要指出,FAQ 中指出:“您应该只根据您所面临的实际问题提出实际的、可回答的问题。”我看不到在哪里”。如前所述,如果您知道另一个 Stack Exchange 站点我应该在哪里发布此内容,请推荐它。
  • @djechlin 你弄错了。问一个关于堆栈溢出的实际编码问题是完全可以的。我想也许你正在考虑programmers.SE。无论如何,请随时在 Meta 上进行澄清。

标签: java sorting quicksort


【解决方案1】:

我不得不摆脱分区,因为你需要i 和j。它应该是这样的:

public void quickSort(int[] a, int left, int right) {

    int i = left; // Was -1 
    int j = right; // Was +1
    int pivot = a[left + (right - left) / 2]; // Pivot is the value of the middle index, not the index itself
    while (i <= j) { // Changed terminating condition
        //   i++;  Not needed
        while (a[i] < pivot) { 
            i++;
        }
        //    j++; Not needed
        while (a[j] > pivot) {
            j--;
        }
        if (i <= j) {  // Changed terminating condition
            swap(a, i, j);
            i++;  // You need to progress the indexes after the swap
            j--;
        }
    }

    System.out.println(Arrays.toString(a));
    if (left < j) {  // Changed condition
        quickSort(a, left, j);
    }
    if (i < right) { 
        quickSort(a, i, right); // was i + 1
    }
}

输出:

[4, 5, 1, 2, 3, 7, 6, 8]
[1, 5, 4, 2, 3, 7, 6, 8]
[1, 3, 2, 4, 5, 7, 6, 8]
[1, 2, 3, 4, 5, 7, 6, 8]
[1, 2, 3, 4, 5, 7, 6, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]

【讨论】:

  • 完美运行。先生,您是绝对的传奇。我会研究你的代码和我的代码之间的差异,并尝试从中学习。
  • 没问题。如果您需要任何说明,请在此处评论。
  • 您好,您能进一步解释一下这一行吗? int pivot = a[left + (right - left) / 2];
  • @OliverCiappara 该行表示我们选择位于left 和right 中间的元素作为pivot 的值。
【解决方案2】:

很明显,你已经得到了你接受的答案。但是我要提一下,您的分区逻辑可以更容易地实现,只有一个 for(或 while)循环,也没有嵌套循环:

int partition(final int[] a, final int left, final int right) {
        // set the last element as pivot
        final int pivot = a[right];
        int i = left - 1, j = left;
        for (; j < right; j++) 
            if (a[j] < pivot) {
                i++;
                swap(a, i, j);
            }       
        // swap a[i+1] and pivot
        swap(a, i + 1, right);
        return i + 1;
    }

在您的快速排序方法中:

if (left < index)
  quickSort(a, left, index-1);
if (index < right)
  quickSort(a, index + 1, right);

希望对你有帮助

【讨论】:

  • 这一切都有帮助。我会研究这个并尝试从中学习。谢谢
  • @AndrewMartin , j = left - 就在那里
  • @AndrewMartin 不,我不需要int j,它是在 for 循环之前声明的。阅读代码,伙计。
  • 道歉 - 我不知道你可以声明这样的 for 循环。很抱歉这个愚蠢的建议,再次感谢您的回答。
【解决方案3】:
int pivot = a[0];

应该是

int pivot = a[left];

即,将swap (a, i, j); 更改为swap (a, i--, j++); 和everything appears to work fine。

上述变化的原因:

枢轴应该是范围内的第一个元素,而不是第一个元素。

也不应该在这个中间,像这里:

int pivot = a[(left + right) / 2];

您希望枢轴成为哪个元素并不重要,最简单的方法是始终将所选元素与第一个元素交换,然后照常继续。可能还有其他的做事方式,但可能会更复杂。

所以你可以说:

swap(left, (left + right) / 2);
int pivot = a[left];

与上面的非常相似(不完全相同),只是更容易处理。

【讨论】:

  • 别人推荐pivot = a[left + (right - left) / 2],我认为符合三规则的中位数。然而,这是我一开始真正打算做的,当然完全错了。谢谢你。
  • @AndrewMartin a[left + (right - left) / 2] - 这不是 3 的中位数(甚至不接近)。对于 3 的中位数,找到第一个、中间和最后一个元素的中位数,然后 将该元素与第一个位置的元素交换,然后照常继续。
  • 刚用谷歌搜索了三个的中位数,就意识到我错了。谢谢
【解决方案4】:

我认为partition 方法应该返回j 而不是i。

您的代码中的另一个问题是您的停止条件:

我将其更改为单个条件,而不是两个单独的条件:

if (left < right)  {

  do partition & recursive calls

}

完整代码:

public void quickSort(int[] a, int left, int right) {
    if (left < right) {
      int index = partition(a, left, right);
      quickSort(a, left, index);
      quickSort(a, index + 1, right);
    }
}

private int partition (int[] a, int left, int right) {
    int i = left - 1;
    int j = right + 1;
    int pivot = a[(left+right)/2];

    while (i < j) {

        i++;

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

        j--;

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

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

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

【讨论】:

  • 它可能需要它,但不幸的是它会返回 StackOverflow 错误 - 可能是由于其他地方的错误。
  • 您应该将该更改应用于您的原始代码。您在 edit2 中的更改是错误的。您必须在循环之前更改 i 和 j,因为它们被初始化为您正在分区的数组范围之外的值。
  • 我已将我的个人副本改回 edit2 之前,并将 return int 更改为 j - 但是,它仍然会产生 StackOverflow 错误
  • 你有调试器吗? StackOverflow 表示分区方法不对输入进行分区(即其中一个分区为空),导致无限递归。
  • 是的,我现在正在 Eclipse 调试器上完成每一步(使用您的编辑)。只是不知道如何阻止错误!
猜你喜欢
  • 2011-08-31
  • 1970-01-01
  • 2010-09-14
  • 2015-01-13
  • 2020-11-16
  • 2012-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多