1. 程式人生 > >n個數中找最大的k個數問題求解(要求複雜度為O(n))

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很小時。