【问题标题】:choosing pivot for the quickselect using median implemented in java?使用java中实现的中值选择快速选择的枢轴?
【发布时间】:2013-12-23 21:06:11
【问题描述】:

我在 github 中找到了此代码,用于 quickselect 算法,也称为 order-statistics。这段代码工作正常。

我不明白medianOf3方法,它应该按排序顺序排列第一个,中间和最后一个索引。但实际上在调用medianof3 方法后输出数组时并没有。 除了最后一次调用swap(list, centerIndex, rightIndex - 1); 之外,我可以按照这个方法来了解它在做什么。有人能解释一下为什么会这样吗?

import java.util.Arrays;



/**
* This program determines the kth order statistic (the kth smallest number in a
* list) in O(n) time in the average case and O(n^2) time in the worst case. It
* achieves this through the Quickselect algorithm.
*
* @author John Kurlak <john@kurlak.com>
* @date 1/17/2013
*/
public class Quickselect {
   /**
* Runs the program with an example list.
*
* @param args The command-line arguments.
*/
   public static void main(String[] args) {
       int[] list = { 3, 5, 9, 10, 7, 40, 23, 45, 21, 2 };
       int k = 6;
       int median = medianOf3(list, 0, list.length-1);
       System.out.println(median);
       System.out.println("list is "+ Arrays.toString(list));
       Integer kthSmallest = quickselect(list, k);

       if (kthSmallest != null) {
           System.out.println("The kth smallest element in the list where k=" + k + " is " + kthSmallest + ".");
       } else {
           System.out.println("There is no kth smallest element in the list where k=" + k + ".");
       }
       System.out.println(Arrays.toString(list));
   }

   /**
* Determines the kth order statistic for the given list.
*
* @param list The list.
* @param k The k value to use.
* @return The kth order statistic for the list.
*/
   public static Integer quickselect(int[] list, int k) {
       return quickselect(list, 0, list.length - 1, k);
   }

   /**
* Recursively determines the kth order statistic for the given list.
*
* @param list The list.
* @param leftIndex The left index of the current sublist.
* @param rightIndex The right index of the current sublist.
* @param k The k value to use.
* @return The kth order statistic for the list.
*/
   public static Integer quickselect(int[] list, int leftIndex, int rightIndex, int k) {
       // Edge case
       if (k < 1 || k > list.length) {
           return null;
       }

       // Base case
       if (leftIndex == rightIndex) {
           return list[leftIndex];
       }

       // Partition the sublist into two halves
       int pivotIndex = randomPartition(list, leftIndex, rightIndex);
       int sizeLeft = pivotIndex - leftIndex + 1;

       // Perform comparisons and recurse in binary search / quicksort fashion
       if (sizeLeft == k) {
           return list[pivotIndex];
       } else if (sizeLeft > k) {
           return quickselect(list, leftIndex, pivotIndex - 1, k);
       } else {
           return quickselect(list, pivotIndex + 1, rightIndex, k - sizeLeft);
       }
   }

   /**
* Randomly partitions a set about a pivot such that the values to the left
* of the pivot are less than or equal to the pivot and the values to the
* right of the pivot are greater than the pivot.
*
* @param list The list.
* @param leftIndex The left index of the current sublist.
* @param rightIndex The right index of the current sublist.
* @return The index of the pivot.
*/
   public static int randomPartition(int[] list, int leftIndex, int rightIndex) {
       int pivotIndex = medianOf3(list, leftIndex, rightIndex);
       int pivotValue = list[pivotIndex];
       int storeIndex = leftIndex;

       swap(list, pivotIndex, rightIndex);

       for (int i = leftIndex; i < rightIndex; i++) {
           if (list[i] <= pivotValue) {
               swap(list, storeIndex, i);
               storeIndex++;
           }
       }

       swap(list, rightIndex, storeIndex);

       return storeIndex;
   }

   /**
* Computes the median of the first value, middle value, and last value
* of a list. Also rearranges the first, middle, and last values of the
* list to be in sorted order.
*
* @param list The list.
* @param leftIndex The left index of the current sublist.
* @param rightIndex The right index of the current sublist.
* @return The index of the median value.
*/
   public static int medianOf3(int[] list, int leftIndex, int rightIndex) {
       int centerIndex = (leftIndex + rightIndex) / 2;

       if (list[leftIndex] > list[rightIndex]) {
           swap(list, leftIndex, centerIndex);
       }

       if (list[leftIndex] > list[rightIndex]) {
           swap(list, leftIndex, rightIndex);
       }

       if (list[centerIndex] > list[rightIndex]) {
           swap(list, centerIndex, rightIndex);
       }

       swap(list, centerIndex, rightIndex - 1);

       return rightIndex - 1;
   }

   /**
* Swaps two elements in a list.
*
* @param list The list.
* @param index1 The index of the first element to swap.
* @param index2 The index of the second element to swap.
*/
   public static void swap(int[] list, int index1, int index2) {
       int temp = list[index1];
       list[index1] = list[index2];
       list[index2] = temp;
   }
}

【问题讨论】:

    标签: java algorithm median median-of-medians quickselect


    【解决方案1】:

    所以我编写了原始代码,但我在使其可读性方面做得很差。

    回过头来看,我觉得那行代码没必要,但我觉得是小优化。如果我们删除这行代码并返回centerIndex,它似乎可以正常工作。

    不幸的是,它执行的优化应该从medianOf3() 中重构出来并移至randomPartition()

    本质上,优化是我们希望在分区之前尽可能地“部分排序”我们的子数组。原因是这样的:我们的数据排序越多,我们未来的分区选择就会越好,这意味着我们的运行时间有望比 O(n^2) 更接近 O(n)。在randomPartition() 方法中,我们将枢轴值移动到我们正在查看的子数组的最右侧。这会将最右边的值移动到子数组的中间。这是不希望的,因为最右边的值应该是“更大的值”。我的代码试图通过将枢轴索引放在最右边的索引旁边来防止这种情况。然后,当枢轴索引与randomPartition() 中最右边的索引交换时,“较大的”最右边的值不会移动到子数组的中间,而是停留在右边。

    【讨论】:

      【解决方案2】:

      函数medianOf3是定义左中位数和右位数的顺序。最后声明

      swap(list, centerIndex, rightIndex - 1)

      用于实现排序的以下前置条件:

      然而, 而不是递归到双方,如快速排序,快速选择 只递归到一侧——它所在的元素的一侧 寻找。这降低了 O(n log n) 的平均复杂度(在 快速排序)到 O(n)(在快速选择中)。

      然后算法继续:

         for (int i = leftIndex; i < rightIndex; i++) {
             if (list[i] <= pivotValue) {
                 swap(list, storeIndex, i);
                 storeIndex++;
             }
         }
      

      为了

      枢轴左侧的值小于或等于 枢轴和枢轴右侧的值大于 枢轴。

      【讨论】:

      • 我不明白如何实现排序的前提条件:swap(list, centerIndex, rightIndex - 1)。忽略这一点,仍然会在快速选择中递归到一侧。
      • 此时中心元素小于右元素。因此,当您将中心元素与 (rightIndex - 1)th 元素交换时,您已经订购了该子列表的最后两个元素。之后,您将中心元素的较小元素(位于位置 (rightIndex - 1) 的位置向左移动,向右移动更大。最后,您将中心元素放在右侧位置:swap(list, rightIndex, storeIndex)
      • “之后,您将在位置上移动中心元素的较小元素......”是什么意思。我不明白这一点。是否可以在一个包含 5 个或更少元素的简单数组中进行说明?这很容易理解。谢谢
      • 阅读我的解释。然后在此处开始代码:ideone.com/bJyB6s 每次交换打印后,请仔细阅读我编写并可视化的内容。
      • 可视化帮助你理解了吗?您需要任何其他说明吗?
      猜你喜欢
      • 2010-09-14
      • 1970-01-01
      • 1970-01-01
      • 2019-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-20
      • 2017-02-25
      相关资源
      最近更新 更多