最小的k个数(Java)

题目:

输入n个整数,找出其中最小的k个数。例如输入4、 5、 1、 6、 2、 7、 3、 8这8个数字,则最小的4个数字是1、 2、 3、 4。

思路:O(n)的算法,只有当我们可以修改输入的数组是可用

经典常用的算法,快速排序的精髓利用快速排序划分的思想,每一次划分就会有一个数字位于以数组从小到达排列的的最终位置index;位于index左边的数字都小于index对应的值,右边都大于index指向的值;所以,当index > k-1时,表示k个最小数字一定在index的左边,此时,只需要对index的左边进行划分即可;当index < k - 1时,说明index及index左边数字还没能满足k个数字,需要继续对k右边进行划分

代码实现:

public ArrayList<Integer> getLeastNumbers(int nums[], int k){
	ArrayList<Integer> list = new ArrayList<Integer>();
	int lens = nums.length;
	if(nums == null || lens == 0 || k > lens || k <= 0){
		return list;
	}
	int start = 0;
	int end = lens - 1;
	int index = partition(nums, start, end);
	while(index != k-1){
		if(index > k-1){
			end = index - 1;
			index = partition(nums, start, end);
		}else{
			start = index + 1;
			index = partition(nums, start, end);
		}
	}
	for (int i = 0; i < k; i++) {
		list.add(nums[i]);
	}
	return list;
}
private int partition(int[] nums, int start, int end) { //快排
	int privotKey = nums[start];
	while(start < end){
		while(start < end && privotKey <= nums[end]){
			--end;
		}
		swap(nums, start, end);//交换位置,使大于privotkey的值位于数组右边
			
		while(start < end && privotKey >= nums[start]){
			++start;
		}
		swap(nums, start, end);//交换位置,使小于privotkey的值位于数组左边
	}
	return start;
}
private void swap(int[] nums, int start, int end) { //交换数组元素位置
	int temp = nums[start];
	nums[start] = nums[end];
	nums[end] = temp;
}
public static void main(String[] args) { //测试
	int nums[] = {4, 5, 1, 6, 2, 7, 3, 8};
	Main m = new Main();
	ArrayList<Integer> list = m.getLeastNumbers(nums, 4);
	for (int i = 0; i < list.size(); i++) {
		System.out.println(list.get(i));
	}
}

思路:O(nlogk)的算法,特别适合处理海量数据

我们可以创建一个容量为k的数据容器来存储最小的k个数字,接下来我们每次从输入的n个整数中读入一个数。如果容器中已有的数字少于k个,则直接把这次读入的整数放入容器之中;如果容器中已有k个数字了(即容器满了),我们就不能再插入数字了,只能去替换容器中已有的数字。替换的规则是,我们拿待插入的数字和容器中k个数字中的最大值进行比较,如果大于容器中的最大值,则抛弃这个整数,否则用这个整数去替换这个数字。

故,容器满了之后,我们需要做3件事:一是在k个整数中找到最大数;二是有可能在这个容器中删除这个最大数;三是有可能会在这个容器中插入一个新数字。用二叉树实现这个容器,我们能在O(logk)时间内实现这三步操作。因此对于n个数字而言,总的时间效率就是O(nlogk)。

容器的实现用数据结构中的最大堆,因为其根结点的值永远是最大的结点值。我们用红黑树来实现我们的最大堆容器。而TreeSet类实现了红黑树的功能,它的底层是通过TreeMap实现的,TreeSet中的数据会按照插入数据自动升序排序。我们只需要将数据放入TreeSet中,其就会为我们实现排序。

代码实现:

public static ArrayList<Integer> getLeastNumbers(int []nums, int k){
	ArrayList<Integer> list = new ArrayList<Integer>();
	int lens = nums.length;
	if(nums == null || lens == 0 || k > lens || k <= 0){
		return list;
	}
	TreeSet<Integer> kSet = new TreeSet<Integer>();
	for (int i = 0; i < lens; i++) {
		if(kSet.size() < k){
			kSet.add(nums[i]);
		}else if(nums[i] < kSet.last()){
			kSet.remove(kSet.last());
			kSet.add(nums[i]);
		}
	}
	Iterator<Integer> iterator = kSet.iterator();
	while(iterator.hasNext()){
		list.add(iterator.next());
	}
	return list;
}

小思:

借助于一个容器来存储这k个最小的数,有两个优点:(1)没有修改输入的数据;(2)这个算法适合海量数据的输入。算法的题目可以改成从海量数据中找出k个最小的数。第一个利用快速排序的思想的时间复杂度为O(n),后一个借助于一个容器的思想的时间复杂度为O(nlogk)。

猜你喜欢

转载自blog.csdn.net/u013132035/article/details/80665655