尋找最大的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