算法题004 寻找最大的K个数
寻找最大的K个数
题目描述
有很多个无序的数,怎么选出其中最大的若干个数?
即,从n个数中选出最大的K个数。
解法一
先假设元素的数量不大,例如在几千个左右,在这种情况下,我们就排序吧。
在这里,快速排序或堆排序都是不错的选择,他们的平均时间复杂度都是O(nlog2n),然后取出钱K个,O(K)。
总的时间复杂度仍然是O(nlog2n)。
可以注意到,即便是K=1的情况,上面的算法复杂度仍然是O(nlog2n),而显然,我们可以通过n-1此的比较和交换得到结果,不需要对整个数组进行排序。
要避免做后面n-K个数的排序,可以使用部分排序算法,选择排序和冒泡排序都是不错的选择。
把n个数中的前K个数排序出来,复杂度是O(n*K)。
哪一个更好呢?O(nlog2n) 和O(n*K)?这取决于K的大小,在K<log2n的情况下,可以选择部分排序。
解法二
回忆一下快速排序,快排中的每一步,都是将数据分为两组,其中一组的任何数都小于另一组中的任何数,不断地对数据进行分割直到不能再分即完成排序。
假设n个数存储在数组S中,从S中找出一个元素X,它把数组分为比它大的和比它小的两部分,假设比它大的一部分数组是Smax,比它小的部分是Smin。
这时有两种可能:
1.Smax中元素不足K个,说明Smax中的所有数和Smin中最大的K-|Smax|个元素就是数组S中最大的K个数;
2.Smax中元素的个数大于或等于K,则需要返回Smax中最大的K个元素。
这样递归下去,问题的规模不断地变小,平均时间复杂度O(n * log2K)。
解法三
寻找n个数中最大的K个数,本质上就是寻找这K个数中最小的那个,也就是第K大的数。
可以使用二分搜索的策略。对于一个给定的数p,可以在O(n)的时间复杂度内找出所有不小于p的数。
(此方法详述部分略)。
解法四:如果N非常非常大呢?
前面三个解法都需要对数据访问多次,如果n很大呢?100亿?这个时候数据不能全部装入内存,所以要求尽可能少地遍历所有数据。
前K个数中最大的K个数是一个退化的情况,所有K个数就是最大的K个数,如果考虑第K+1个数,则它和前面K个数的最小值进行比较,比其大则替换它。
如果用一个数组来存储最大的K个数,每加入一个数X,就扫描一遍数组,得到数组中最小的数Y。X和Y进行比较,替换它或者保持原数组不变。这种方法,所耗费的时间复杂度为O(n * K)。
进一步,可以用容量为K的最小堆来存储最大的K个数。最小堆的堆顶元素就是最大K个数中最小的一个。每次新考虑一个数X,如果X小于堆顶则舍弃,如果X大于堆顶,那么用X替换堆顶,然后更新堆来维持堆的性质。(因为X可能并不是最小值,所以堆结构需要更新)。更新过程花费的时间复杂度为O(log2K)。
因此,算法的时间复杂度为O(n * log2K),这实际上是部分执行了堆排序的算法。
解法五
如果所有n个数都是整数,且它们的取值范围不太大,可以考虑申请空间,记录每个整数出现的次数,然后再从大到小取最大的K个。
比如用数组count,count[i]表示整数i出现的次数。
极端情况下,如果n个整数各不相同,只需要一个bit来存储这个整数是否存在。
实际情况下,并不一定能保证所有元素都是正整数,且取值范围不太大。但是这种方法仍然可以推广适用。比如把取值区间分成多块,然后统计各个小区间中元素的个数。可以知道第K大的元素在哪一个小区间,然后再对那个小区间继续进行分块处理。
参考资料
《编程之美》2.5节。
本题目其他相关链接:
链接1:http://blog.csdn.net/koala002/article/details/6415485
链接2:http://www.cnblogs.com/luxiaoxun/archive/2012/08/06/2624799.html
本博客关于排序算法的回顾:
排序算法:http://www.cnblogs.com/mengdd/archive/2012/11/24/2786346.html