一、背景
實際應用中,我們經常面臨這樣的問題,即從一個序列S中選擇其中最大的K個數(一般情況下K遠小於|S|),我們將這種問題稱爲TopK問題。
舉一個例子,美劇《權利的遊戲》中的每個人物,觀衆都會對其進行選擇支持或不支持,這樣每個人物都會都應一個熱度值,這個值可以是支持者的數量,我們可以發現在騰訊視頻中該劇中熱度排名前3的人物加以了高亮標識,如下圖:
諸如此類的應用場景很多,本文藉助快速排序的思想,給出解決此問題的一個時間複雜度爲O(N)的算法。
二、快速排序思想
爲了引出本文基於快排TopK的思想,先給出快排的算法描述,令S爲待排序集合,|S|表示集合元素數量。
1、如果 S 中元素個數是0或1,則返回。
2、取 S 中任一元素 v, 稱之爲樞紐元。
3、將 S - {v} (S中其餘元素) 分成兩個不相交的集合: 和 。
4、返回 {後,繼隨 , 繼而 }。
本人博客https://blog.csdn.net/gaoxueyi551/article/details/46778181中的代碼便是該思想的實現,可供參考。
三、基於快速排序的TopK算法
其實,只要稍加修改快排算法,即可實現平均時間複雜度爲O(N)的TopK算法,我們稱之爲QuickSelect(S, K)。
1、如果|S| = 1,返回S,否則若|S| < 20,則使用選擇排序對S排序,選擇最大的K個元素返回。(同快速排序步驟1)
2、選取一個樞紐元 v 屬於 S。(同快速排序2)
3、將集合 S - {v} 分割成 S1 和 S2。 (同快速排序步驟3)
4、如果K <= |S1| ,則K個元素必然全部位於集合S1中,並返回QuickSelect(S1, K);如果K = |S1| + 1,則集合S1 與 樞紐元 v 恰好是所求的K個數,我們將 S1 和 v 一併返回;如果K > |S1| + 1,那麼S1和樞紐元v必然是K個元素的一部分,剩餘K - |S1| - 1 個元素必然存在集合 S2 中,因此我們應該返回 S1 + v + QuickSelect(S2, K - |S1| - 1)。
可以看到,該算法前3步和快速排序是相同的,唯一區別在於步驟4。仔細分析該算法可得如下結論:
- 快速選擇算法的遞歸調用次數是快速排序的一半。
- 兩種算法的最壞情形都是O(N*N),即集合已排序的情況。
- 算法平均時間複雜度爲O(N)。
結論2易於理解。分析算法可知,每一輪遞歸開始之前,我們已經消去了1個集合,遞歸僅作用在另外一個集合,結論2於是成立。結論3的推導依賴結論1。
在理想情況下,樞紐元 v 的選擇使每輪遞歸都近似的將當前集合等分,故最多需要遞歸O(logN)次:
第1次遞歸對應的集合長度爲 |S / 2|,
第2次遞歸對應的集合長度爲 |S / 4|,
第3次遞歸對應的集合長度爲 |S / 8|,
......
第log(N) 次遞歸對應的集合長度爲 1,
將O(logN)次遞歸的集合長度相加,
Sum = |S / 2| + |S / 4| + |S / 8| + ...... + 1
這是等比數列,根據等比數列求和方法可得Sum大約等於2N,故快速選擇的平均時間複雜度爲O(N)。