尋找第K大元素的八大演算法 原始碼及拓展

2021-07-05 16:27:45 字數 2873 閱讀 9440

所謂「第(前)k大數問題」指的是在長度為n(n>=k)的亂序陣列中s找出從大到小順序的第(前)k個數的問題。

第k大問題可以是現實問題,譬如競價排名中的第k個排名,或者多個出價者中的第k大**等等。

很好理解,利用快排對所有元素進行排序,然後找到第k個元素即可。

也是初級解法,且很雞肋。快排只有第一次時間複雜度較高,後面每次查詢時間複雜度均為常數。而選擇排序不論是排序還是查詢,時間複雜度都很高。

1. sa中元素的個數小於k,則sb中的第k-|sa|個元素即為第k大數;

2. sa中元素的個數大於等於k,則返回sa中的第k大數。時間複雜度近似為o(n)。

3.遞迴以上兩步直到找到為止。

這其實也是個排序演算法。

這個演算法的最大優勢在於,如果陣列非常非常大的話,利用普通的排序是爆記憶體的。用它的話,則只用到k的記憶體。

與上述思路4類似,不同的是在對元素陣列原地建最大堆o(n)後,然後提取k次,但是每次提取時,換到頂部的元素只需要下移頂多k次就足夠了,下移次數逐次減少(而上述思路7每次提取都需要logn,所以提取k次,思路7需要k*logn。而本思路8只需要k^2)。此種方法的複雜度為o(n+k^2)。每次提取k後,換到頂部的元素需要下移的最大次數越少,保證了最壞情況下的效率。

下面是演算法主要步驟:

其中有些細節的處理,主要是邊界問題還是比較關鍵,後面會給出這些問題。

資料有n個,取出最小的k個數字

終止條件:n=1時,返回的即是i小元素。

演算法步驟:

step1:將n個元素每5個一組,分成n/5(上界)組,最後的乙個組的元素個數為n%5,有效的組數為n/5。

step2:取出每一組的中位數,最後乙個組的不用計算中位數,任意排序方法,這裡的資料比較少只有5個,

可以用簡單的氣泡排序或是插入排序。

step3: 將各組的中位數與陣列開頭的資料在組的順序依次交換,這樣各個組的中位數都排在了資料的左邊。

遞迴的呼叫中位數選擇演算法查詢上一步中所有組的中位數的中位數,設為x,偶數個中位數的情況下設定為選取中間小的乙個。

step4: 按照x劃分,大於或者等於x的在右邊,小於x的在左邊,關於setp4資料的劃分,中位數放在左邊或是右邊會有些影響。

後面的**除錯將會看到。

step5:step4中劃分後資料後返回乙個下表i,i左邊的元素均是小於x,i右邊的元素包括i都是大於或是等於x的。

若i==k,返回x;

若i若i>k,在大於等於x的元素中遞迴查詢第i-k小的元素。

中位數問題其實是第k大問題的乙個自問題。可以用所有第k大問題的演算法來解答。我們在這裡提出幾個更加嚴格的中位數問題。

我們假定在集合中有偶數個元素時,中位數是指較小的那個中間數。用兩個堆,乙個大頂堆包含集合裡較小的(n+1)/2個數,另乙個小頂堆包含集合裡較大的另一半數。查詢中位數時,直接看大頂堆的堆頂元素即可。插入元素時,先將其與兩個堆頂元素比較,以決定插入哪個堆。如果插入之後兩堆的元素個數之差超過了1,就把多的那個堆的堆頂元素插入到另一堆裡。刪除元素時,將中位數刪掉之後,同樣調整兩個堆的元素個數。

這又是乙個變體,可以擴充套件為求兩個有序陣列的第k位數。最簡單的思路當然是合併陣列,然後再直接求有序陣列的第k位數,這是乙個o(n)的解法。然而,對於「kth element in 2 sorted array」一類問題來說, 如下圖,兩個中位數 a[m/2] 和 b[n/2], 可以將陣列劃分為四個部分。而丟棄哪乙個部分取決於兩個條件:1, (m/2 + n/2)?k;2,a[m/2] ? b[n/2];

如果 (m/2 + n/2) > k,那麼意味著,當前中位數取高了,正確的中位數要麼在 section 1或者section3中。如果a[m/2] > b[n/2], 意味著中位數肯定不可能在section 2裡面,那麼新的搜尋可以丟棄這個區間段了。同理可以推斷出餘下三種情況,如下所示:

if (m/2+n/2+1) > k && am/2 > bn/2 , drop section 2if (m/2+n/2+1) > k && am/2 < bn/2 , drop section 4if (m/2+n/2+1) < k && am/2 > bn/2 ,  drop section 3if (m/2+n/2+1) < k && am/2 < bn/2 ,  drop section 1
簡單的說,就是或者丟棄最大中位數的右區間,或者丟棄最小中位數的左區間。

附上**如下:

1. 如果需要找出n個數中最大的k個不同的浮點數呢?比如,含有10個浮點數的陣列(1.5,1.5,2.5,3.5,3.5,5,0,- 1.5,3.5)中最大的3個不同的浮點數是(5,3.5,2.5)。

解答:上面的解法均適用,需要注意的是浮點數比較時和整數不同,另外求hashkey的方法也會略有不同。

2. 如果是找第k到第m(0

4. 在實際應用中,還有乙個「精確度」的問題。我們可能並不需要返回嚴格意義上的最大的k個元素,在邊界位置允許出現一些誤差。當使用者輸入乙個query的時候,對於每乙個文件d來說,它跟這個query之間都有乙個相關性衡量權重f (query, d)。搜尋引擎需要返回給使用者的就是相關性權重最大的k個網頁。如果每頁10個網頁,使用者不會關心第1000頁開外搜尋結果的「精確度」,稍有誤差是可以接受的。比如我們可以返回相關性第10 001大的網頁,而不是第9999大的。在這種情況下,演算法該如何改進才能更快更有效率呢?網頁的數目可能大到一台機器無法容納得下,這時怎麼辦呢?

5. 如第4點所說,對於每個文件d,相對於不同的關鍵字q1, q2, …, qm,分別有相關性權重f(d, q1),f(d, q2), …, f(d, qm)。如果使用者輸入關鍵字qi之後,我們已經獲得了最相關的k個文件,而已知關鍵字qj跟關鍵字qi相似,文件跟這兩個關鍵字的權重大小比較靠近,那麼關鍵字qi的最相關的k個文件,對尋找qj最相關的k個文件有沒有幫助呢?

解答:肯定是有幫助的。在搜尋關鍵字qj最相關的k個文件時,可以在qj的「近義詞」相關文件中搜尋部分,然後在全域性的所有文件中在搜尋部分。

尋找陣列中的第K大元素

一 簡述 使用資料結構堆,實現查詢陣列中的第k大元素。流程如下 陣列內容堆化 因為是查詢第k大,所以使用大根堆 依次取出並刪除堆的根節點 將剩餘元素堆化 取到第k個即為目標元素。二 實現 include include using namespace std intfindkth vector in...

經典演算法題 尋找陣列中第k大的元素

問題描述 在陣列中找到第 k 大的元素。要求時間複雜度為o n 空間複雜度為o 1 分析 此類問題為排序問題,主要難點在於時間複雜度為o n 採用快速排序演算法進行排序 function quicksort nums,k,start,end var left start,right end var ...

尋找陣列中的第K大的元素

遇到了乙個很簡單而有意思的問題,可以看出不同的演算法策略對這個問題求解的優化過程。問題 尋找陣列中的第k大的元素。最簡單的想法是直接進行排序,演算法複雜度是o n logn 這麼做很明顯比較低效率,因為不要求別的資訊只要計算出第k大的元素。當然,如果在某種情況下需要頻繁訪問第k大的元素就可以先進行一...