n个数中找最大的k个数问题求解(要求复杂度为O(n))

 

首先我们都知道可以将n个元素建一个最(大|小)堆,O(n)。

下面一个很常见的做法就是,每步从堆顶拿掉一个元素,拿k次,就把前k个元素拿出来了。但问题是,每步拿掉一个元素之后,都需要log(n)的时间来将堆再次最­(大|小)化。所以拿k次的复杂度就是
klog(n)。有没有可能更低呢?

分析一下上面这个做法,可以发现有冗余操作:当拿掉一个元素之后,我们开始重新最大化堆,这个时候注意,并不需要往下调整log(n)层,因为顶多就要拿k个元­素,所以往下调整k层就足够了,再下面的元素就不管了,反正它们肯定无缘前k。根据这个办法,可以将klog(n)优化到k(min(k,
logn))。

但这还不够,还可以继续优化,为什么呢?因为在上面的方法中,为了保证将潜在前k的元素都调整了,我们必须往下调整k层,但实际上从最终结果来看有可能很大一部­分调整还是白费了,毕竟,往下调整k层就涉及到2^k个元素,而我们最多只取其中的k个。

不妨重新从最直观的角度来看一下我们的需求:当最大堆建立之后,我们假想站在堆顶往下看,上面的元素掩盖了下面的元素,我们只看到堆顶元素。我们首先拿掉堆顶元­素。然后暴露出下面的两个儿子,我们从两个儿子中选出一个大的拿掉,暴露出它的两个儿子,这下我们视野中有三个元素了,我们从三个中选出最大的,拿掉它,又继续­暴露出它的两个儿子..
如此不断拿直到拿满k个。这个方法应该是最优的,因为它没有费劲地去调整不相干的元素,每次都是从最有可能竞争前k的元素们中选择的——为什么呢?根据上次我们­的分析,这种方法每次总是去比较最有可能大小势均力敌的两个元素,从而获得的信息是最大的。

以上是直观模型,为了便于编码实现,我们必须用一种方法来维护"当前被暴露出来的元素们"。由于每次我们都是从当前被暴露的元素们中选择最大的,所以用一个最大­堆来存放它们是最佳办法。

这个方法复杂度是O(n+klogk),但比CLRS上下界O(n)的算法常数要低得多,所以实践中应该还是很不错的,尤其是当k相比于n很小时。

你可能感兴趣的:(n个数中找最大的k个数问题求解(要求复杂度为O(n)))