尋找最小的k個數

今天上午看了篇博文,這篇博文是對另一篇博文的總結,兩篇博文都是在討論一個算法——尋找最小的K的數,感覺兩篇文章寫得都很不錯,值得借鑑和學習,所以做了一個自我的一個小總結,兩篇博文的連接如下:

http://blog.csdn.net/huagong_adu/article/details/6901924

http://blog.csdn.net/v_JULY_v/article/details/6370650

我主要就看了第一篇博文,第一篇博文也是在第二篇博文的基礎上總結的。

算法的題目:如何在一堆數據中找出最小的K個數

一共有以下幾種方法,效率由低到高:

1. 先對數據進行排序(插入排序,快速排序,whatever),然後取出前K個數

2. 聲明一個K大小的數組,先從這堆數據中裝入前K個數,找出這K個數中的最大的數Max(K),然後從第K+1個數開始向後找,如果有小於這個Max(K)的,則替換掉這個數,然後從這K個數中重新找出最大的Max(K),這樣一直向後掃描,得到結果。這個算法的時間複雜度最壞爲O(kn)。

3. 掃描數據堆K遍,每遍找出最小的那個數,複雜度爲O(kn)。

4. 使用最大堆:先用數據中的前K個數建立一個最大堆(根元素大於左子樹,同時大於右子樹,以此類推),建立堆的複雜度爲O(K),然後從第K+1個數開始向後掃描,遇到小於堆頂元素時替換掉堆頂元素,更新堆,這個操作的時間複雜度爲O(logK)。所以總的時間是O(K + (n - k) * logK) = O (n * logK),比O(nK)稍微好一點。

5. 使用最小堆:這次是將這個數據建一個最小堆(時間複雜度O(n)),然後取出堆頂元素,每取完一次更新一次堆(O(logn)),取k次,所以總的時間複雜度是O(n + k * logn)

注意:可以證明O(n + k * logn)< O (n * logK),即使用最小堆的方法優於使用最大堆的方法,但是在實際應用中兩者的時間上的差距很小。但是請注意,在空間複雜度上最大堆需要O(k),而最小堆需要O(n),所以綜合來講,最大堆方法比最小堆方法有優勢。

6. 對最小堆方法的改進:每次取走堆頂元素更新堆時,正常是把堆中最後一個元素放到堆頂(暫且稱爲 !Top),然後調整堆把 !Top下調到他應該在的位置。改進後, !Top不用下調到他原所應該在的位置,而是下調頂多K次就可以了。具體如下:

建立n的最小堆之後,取走堆頂元素(第一個數),然後將最後的數 !Top調到堆頂,把 !Top下調至多K-1層形成新的堆;接着取走堆頂元素(第二個數),同樣,更新堆的時候 !Top下調至多K-2層...直到取走第K個數時,不再更新堆(此時的堆已經不是最小堆),算法結束,已經取得最小的K個數,最後的“堆”是不是堆已經跟我沒關係了。

改進後的複雜度:建堆O(n),更新堆O(K),K次更新爲O(K*K)=O(K^2),所以總的複雜度是O(n+K^2),比改進前的O(n+K*logn)要好。

7. 用快速排序的思想,先選取一個數作爲基準比較數(作者稱爲“樞紐元”,即pivot),用快速排序的方法把數據分爲兩部分Sa和Sb,Sa中的數據都小於pivot,Sb中的數據都大於pivot。

如果K< |Sa|( |Sa|表示Sa的大小),則對Sa部分用同樣的方法繼續操作;

如果K= |Sa|,則Sa是所求的數;

如果K= |Sa| + 1,則Sa和這個pivot一起構成所求解;

如果K> |Sa| + 1,則對Sb部分用同樣的方法查找最小的(K- |Sa|-1)個數(其中Sa和pivot已經是解的一部分了)

8. 第二篇博客中所提到的BFPRT算法,BFPRT算法本質上是對上述快排思想算法的一種改進,重要改進就是在pivot的選取上,一般的快速排序算法是將數據堆中的第一個或者是最後一個數作爲pivot,而BFPRT算法採用“五分化中位數的中位數”方法取得這個pivot,從而使算法複雜度降低到O(n),具體方法如下:

5個爲一組對數據進行劃分,最後一組數據的個數爲n%5,然後對每組數據用插入排序方法選出中位數,對選出的中位數用同樣的方法繼續選,最後選出這些數的中位數作爲pivot,即可達到O(N)的效率。

發佈了7 篇原創文章 · 獲贊 6 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章