2013-12-23 122 views
1

我发现这个代码在github中的quickselect算法,否则被称为order-statistics。此代码工作正常。使用在java中实现的中值选择快速选择枢轴?

我不明白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 <[email protected]> 
* @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; 
    } 
} 

回答

0

因此,我写了原始代码,但我做了一个糟糕的工作,使它可读。

回想起来,我不认为这行代码是必要的,但我认为这是一个小的优化。如果我们删除代码行并返回centerIndex,它似乎没有任何问题。

不幸的是,它所执行的优化应该被重构为medianOf3(),并且被转移到randomPartition()

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

0

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

swap(list, centerIndex, rightIndex - 1)

用于实现了几分以下前提:

然而, 而不是递归到双方,如快速排序,quickselect 只有递归到一个端 - 与元素一起它是 搜索。这将平均复杂度从O(n log n)(在 快速排序中)降低到O(n)(在快速选择中)。

然后算法继续:

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

为了

这些值到枢轴的左边是小于或等于 枢轴和所述值,以该枢纽的权利大于 。

+0

我不在乎如何实现排序的前提条件:swap(list,centerIndex,rightIndex - 1)'。忽略这一点,仍然会在快速选中时递归到单向。 –

+0

此时中心元素小于右边元素。所以当你用(rightIndex - 1)th元素交换中心元素时,你已经排序了该子列表的最后两个元素。之后,您将中心元素的较小元素(位于右侧的位置(rightIndex - 1))向左移动并且向右移动。最后,将中心元素放到右侧位置:swap(list,rightIndex,storeIndex) – user987339

+0

你是指“在这之后你正在将中心元素的较小元素移动到......”,我不明白这一点,是否可以用一个简单的数组来说明5个或更少的元素?谢谢 –