之前刚刚实现了快速排序算法。现在还有一个要求就是找到一个序列中第K大的数。
我们当然可以用先排序再取值的方法来做,这样的时间复杂度为O(NlogN)。或者使用heap_sort,或者优先队列,则复杂度是O(NlogK)。
那么有没有一种更加高效的方式呢?答案是肯定的。可以使用快速排序的一个变种quick_select,则平均复杂度为O(N),最坏复杂度为O(N^2)。
通过一趟快排过后,序列将被分为(比key小的数,key,比key大的数)三部分,对这一点有疑问的同学可以参考http://blog.csdn.net/morewindows/article/details/6684558
那么假设key的下标为i,如果k < i,则第K大的数必然在快排左边的区域;如果k = i,则key就是第k大的数;如果k > i,则k必然在快排的右边的区域。
接下来递归即可得到第k大的数。
在快排的基础上修改一下即可。代码没有做输入有效性验证,需要的时候请自行添加。
package tech.mrbcy.algorithm.quickselect;
import java.util.Scanner;
public class QuickSelect {
// 获得第K大的数
public int getTopK(int[] nums, int k){
// 这里稍微注意一下k-1,由于数组下标从0开始,第k大的数下标应该是k-1,所以修改了一下
return helper(nums, k - 1, 0, nums.length - 1);
}
private int helper(int[] nums, int k, int left, int right){
if(left == right){
// 只有1个数,那么就是这个数了
return nums[left];
}
int i = left; // 左边标志位
int j = right; // 右边标志位
int key = nums[i]; // key值
while(i < j){
// j向前搜索,找到第一个小于key的值
while(nums[j] >= key && i < j){
j--;
}
// 交换nums[i]和nums[j]
if(nums[j] < key && i < j){
nums[i] = nums[j];
i++; // 填坑后移
}
// i向后搜索,找到第一个大于key的值
while(nums[i] <= key && i < j){
i++;
}
// 交换nums[i]和nums[j]
if(nums[i] > key && i < j){
nums[j] = nums[i];
j--;
}
}
// 现在i 和 j相同,把key填进来
nums[i] = key;
// 递归左边部分和右边部分
if(left < i && k < i){
return helper(nums, k, left, i - 1);
}
if(right > j && k > j){
return helper(nums, k, j + 1, right);
}
return nums[i]; // i == k
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String line = scanner.nextLine();
String[] strs = line.split(",");
int[] nums = new int[strs.length];
int len = strs.length;
for(int i = 0; i < len; i++){
nums[i] = Integer.parseInt(strs[i]);
}
QuickSelect qs = new QuickSelect();
int num = qs.getTopK(nums,3);
// 输出结果
System.out.println(num);
}
}