QuickSelect 的思想与 QuickSort 基本一致。
每次 partition() 结束后可得知 pivot 的位置,自然也就知道 pivot 前后各有多少小于、大于 pivot 的元素。选择时,第 k 大的元素必然只存在于 pivot 的左边或者右边。
- 如果 pivot 前的元素数量多于 k,则说明左边有多于 k 个元素小于 pivot,所以第 k 大的元素必然在左边,可在左边选择第 k 大元素。
- 如果 pivot 前的元素数量少于 k,则说明左边只有不到 k 个小于 pivot 的元素,那么第 k 大的元素必然在右边,可在右边选择第 (k – (pivot 左边元素数量)) 大的元素。
- 如果 pivot 刚好是第 k 个元素,那选择过程就结束了。
实现:
package study;
/**
* @file QuickSelect.java
* @CopyRight (C) http://blog.csdn.net/x_iya
* @Description 快速选择算法
* @author N3verL4nd
* @email lgh1992314@qq.com
* @date 2017/9/24
*/
public class QuickSelect {
private static int partition(int[] arr, int left, int right) {
int pivot = arr[left];
while (left < right) {
while (left < right && arr[right] >= pivot) {
right--;
}
arr[left] = arr[right];
while (left < right && arr[left] <= pivot) {
left++;
}
arr[right] = arr[left];
}
arr[left] = pivot;
return left;
}
private static int QuickSelect(int[] arr, int left, int right, int k) {
if (left < right) {
int pivot = partition(arr, left, right);
if (pivot == k - 1) {
return arr[pivot];
} else if (pivot > k - 1) {
return QuickSelect(arr, left, pivot - 1, k);
} else {
return QuickSelect(arr, pivot + 1, right, k);
}
}
return arr[left];
}
public static void main(String[] args) {
int[] arr = {3, 2, -32, 1442, 203, 37, 874, 2, 193, 34};
System.out.println(QuickSelect(arr, 0, arr.length - 1, 1));
System.out.println(QuickSelect(arr, 0, arr.length - 1, 2));
System.out.println(QuickSelect(arr, 0, arr.length - 1, 5));
System.out.println(QuickSelect(arr, 0, arr.length - 1, 4));
}
}
当然QuickSelect
方法也可以转化为非递归。
public int findKthLargest(int[] nums, int k) {
k = nums.length - k + 1;
int left = 0;
int right = nums.length - 1;
while (left < right) {
int pivotPos = partition(nums, left, right);
if (pivotPos == k - 1) {
return nums[pivotPos];
} else if (pivotPos > k - 1) {
right = pivotPos - 1;
} else {
left = pivotPos + 1;
}
}
return nums[left];
}
参考:
https://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/05DemoQuickSelect.pdf
http://www.cs.yale.edu/homes/aspnes/pinewiki/QuickSelect.html