繁体   English   中英

快速排序实现错误

[英]Quicksort implementation bug

一段时间后,我尝试自己实现 quickSort。 对我来说,实现看起来不错,但一些测试用例显然失败了。

    class MyImplementation {
    public int[] sortArray(int[] nums) {
        quicksort(nums, 0, nums.length - 1);
        return nums;
    }

    private void quicksort(int[] nums, int lo, int hi){
        if(lo < hi){
            int j = partition(nums, lo, hi);
            quicksort(nums, lo, j-1);
            quicksort(nums, j+1, hi);
        }
    }

    private int partition(int[] nums, int lo, int hi){
        int pivot = nums[lo];
        int head = lo + 1;
        int tail = hi;
        while(head < tail){
            while(head < hi && nums[head] < pivot) ++head;
            while(nums[tail] > pivot) --tail;
            if(head < tail){
                swap(nums, head, tail);
                ++head;
                --tail;
            }
        }
        if(nums[head] < pivot)
          swap(nums, lo, head);
        else
          swap(nums, lo, head - 1);  
        return head;
    } 

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

它在许多情况下都会过去,例如:-

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

但它在以下测试用例中失败了:-

[5,71,1,91,10,2,0,0,13,45,7]

我得到的 output 是:-

[0,0,1,2,5,10,7,71,13,45,91]

代替:-

[0,0,1,2,5,7,10,13,45,71,91]

我知道,我可以从某个地方复制以获得正确工作的代码,但是我们可以从算法的角度找出为什么这段代码不起作用吗? 这段代码的逻辑缺陷是什么? 我们如何才能以最少的更改解决此问题并获得完美运行的代码?

这是相当微妙的。 错误在这里:

private void quicksort(int[] nums, int lo, int hi){
    if(lo < hi){
        int j = partition(nums, lo, hi);
        quicksort(nums, lo, j-1);
        quicksort(nums, j+1, hi);
        //              ^^^  j+1 must be j
    }
}

最微妙的部分是,通常不需要使用j作为“高”部分的起点进行递归,但您的分区算法需要它。 所以你会看到各种在不同分区上递归的例子,它们也可以是正确的,结合它们使用的任何分区算法,但是混合的自由度不高。

您的分区算法基于 Hoare 的分区算法,而不是 Lomuto 的算法。 Lomuto 的分区算法将 pivot 放在其最终的 position 中并返回其索引,以便在递归中跳过索引。 Hoare 的分区算法或您的变体并不能完全做到这一点。 例如,在采用分区算法末尾的else分支的情况下,pivot 以head - 1结束,而head处的元素具有不小于 pivot 的任意值,我们所知道的是它属于在“高”分区中,但尚未排序,不能跳过。

结合 Hoare 分区,通常会看到 [lo..p](其中 p 是分区算法返回的索引)和 [p+1..hi] 上的递归,但这在这种情况下不合适因为它的版本返回head而不是tail ,有效地使 p 高了一个,所以分区变成了 [lo.. p-1] 和 [p.. hi]。

当前接受的答案解决了这个问题,但实际上,Lomuto 算法应该能够使用j+1作为第二个分区的开始,因为 pivot 预计在j处,因此不需要成为递归排序的一部分.

真正的问题是您的partition方法并不总是返回 pivot 的索引:

        if(nums[head] < pivot)
          swap(nums, lo, head);
        else
          swap(nums, lo, head - 1);  
        return head;

else的情况下, pivot 不会交换到索引head ,而是交换到head - 1 ,因此在这种情况下返回的索引应该是head - 1

这是一个简单的修复:

        if(nums[head] < pivot)
          swap(nums, lo, head);
        else
          swap(nums, lo, --head);  
        return head;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM