top K 問題解法

2021-09-26 02:08:49 字數 3222 閱讀 6620

**: 

問題描述:

從arr[1, n]這n個數中,找出最大的k個數,這就是經典的topk問題。

栗子:從arr[1, 12]= 這n=12個數中,找出最大的k=5個。

一、排序

排序是最容易想到的方法,將n個數排序之後,取出最大的k個,即為所得。

偽**:

sort(arr, 1, n);

return arr[1, k];

時間複雜度:o(n*lg(n))

分析:明明只需要topk,卻將全域性都排序了,這也是這個方法複雜度非常高的原因。那能不能不全域性排序,而只區域性排序呢?這就引出了第二個優化方法。

二、區域性排序

不再全域性排序,只對最大的k個排序。

冒泡是乙個很常見的排序方法,每冒乙個泡,找出最大值,冒k個泡,就得到topk。

偽**:

for(i=1 to k)

return arr[1, k];

時間複雜度:o(n*k)

分析:冒泡,將全域性排序優化為了區域性排序,非topk的元素是不需要排序的,節省了計算資源。不少朋友會想到,需求是topk,是不是這最大的k個元素也不需要排序呢?這就引出了第三個優化方法。

三、堆思路:只找到topk,不排序topk。

先用前k個元素生成乙個小頂堆,這個小頂堆用於儲存,當前最大的k個元素。

接著,從第k+1個元素開始掃瞄,和堆頂(堆中最小的元素)比較,如果被掃瞄的元素大於堆頂,則替換堆頂的元素,並調整堆,以保證堆內的k個元素,總是當前最大的k個元素。

直到,掃瞄完所有n-k個元素,最終堆中的k個元素,就是猥瑣求的topk。

偽**:

heap[k] = make_heap(arr[1, k]);

for(i=k+1 to n)

return heap[k];

時間複雜度:o(n*lg(k))

畫外音:n個元素掃一遍,假設運氣很差,每次都入堆調整,調整時間複雜度為堆的高度,即lg(k),故整體時間複雜度是n*lg(k)。

分析:堆,將冒泡的topk排序優化為了topk不排序,節省了計算資源。堆,是求topk的經典演算法,那還有沒有更快的方案呢?

四、隨機選擇

隨機選擇算在是《演算法導論》中乙個經典的演算法,其時間複雜度為o(n),是乙個線性複雜度的方法。

這個方法並不是所有同學都知道,為了將演算法講透,先聊一些前序知識,乙個所有程式設計師都應該爛熟於胸的經典演算法:快速排序。

畫外音:

(1)如果有朋友說,「不知道快速排序,也不妨礙我寫業務**呀」…額...

(2)除非校招,我在面試過程中從不問快速排序,預設所有工程師都知道;

其偽**是:

void quick_sort(intarr, int low, inthigh)
其核心演算法思想是,分治法。

分治法有乙個特例,叫減治法。

二分查詢binary_search,bs,是乙個典型的運用減治法思想的演算法,其偽**是:

int bs(intarr, int low, inthigh, int target)
從偽**可以看到,二分查詢,乙個大的問題,可以用乙個mid元素,分成左半區,右半區兩個子問題。而左右兩個子問題,只需要解決其中乙個,遞迴一次,就能夠解決二分查詢全域性的問題。

通過分治法與減治法的描述,可以發現,分治法的複雜度一般來說是大於減治法的:

快速排序:o(n*lg(n))

二分查詢:o(lg(n))

話題收回來,快速排序的核心是:

i = partition(arr, low, high);

這個partition是幹嘛的呢?

顧名思義,partition會把整體分為兩個部分。

更具體的,會用陣列arr中的乙個元素(預設是第乙個元素t=arr[low])為劃分依據,將資料arr[low, high]劃分成左右兩個子陣列:

左半部分,都比t大

右半部分,都比t小

中間位置i是劃分元素

以上述topk的陣列為例,先用第乙個元素t=arr[low]為劃分依據,掃瞄一遍陣列,把陣列分成了兩個半區:

左半區比t大

右半區比t小

中間是t

partition返回的是t最終的位置i。

很容易知道,partition的時間複雜度是o(n)。

畫外音:把整個陣列掃一遍,比t大的放左邊,比t小的放右邊,最後t放在中間n[i]。

partition和topk問題有什麼關係呢?

topk是希望求出arr[1,n]中最大的k個數,那如果找到了第k大的數,做一次partition,不就一次性找到最大的k個數了麼?

畫外音:即partition後左半區的k個數。

問題變成了arr[1, n]中找到第k大的數。

再回過頭來看看第一次partition,劃分之後:

i = partition(arr, 1, n);

如果i大於k,則說明arr[i]左邊的元素都大於k,於是只遞迴arr[1, i-1]裡第k大的元素即可;

如果i小於k,則說明說明第k大的元素在arr[i]的右邊,於是只遞迴arr[i+1, n]裡第k-i大的元素即可;

畫外音:這一段非常重要,多讀幾遍。

這就是隨機選擇演算法randomized_select,rs,其偽**如下:

int rs(arr, low, high, k)
這是乙個典型的減治演算法,遞迴內的兩個分支,最終只會執行乙個,它的時間複雜度是o(n)。

再次強調一下:

分治法,大問題分解為小問題,小問題都要遞迴各個分支,例如:快速排序

減治法,大問題分解為小問題,小問題只要遞迴乙個分支,例如:二分查詢,隨機選擇

通過隨機選擇(randomized_select),找到arr[1, n]中第k大的數,再進行一次partition,就能得到topk的結果。

五、總結

topk,不難;其思路優化過程,不簡單:

全域性排序,o(n*lg(n))

區域性排序,只排序topk個數,o(n*k)

堆,topk個數也不排序了,o(n*lg(k))

分治法,每個分支「都要」遞迴,例如:快速排序,o(n*lg(n))

減治法,「只要」遞迴乙個分支,例如:二分查詢o(lg(n)),隨機選擇o(n)

topk的另乙個解法:隨機選擇+partition

知其然,知其所以然。

思路比結論重要。

N陣列 topK問題 解法

題目描述 有n個長度不一的陣列,所有的陣列都是有序的,請從大到小列印這n個陣列整體最大的前k個數。例如,輸入含有n行元素的二維陣列可以代表n個一維陣列。219,405,538,845,971 148,558 52,99,348,691 再輸入整數k 5,則列印 top 5 971,845,691,5...

topK問題的幾種解法及C 實現

topk問題是經典的演算法問題,其大意是從乙個序列中找出最小 大 的k個數,面對這個問題最簡單的方法當然是先排序後取前k個數,但這樣有些浪費時間,比較經典的方法是借助快排和堆排的思想。注 為了方便討論,以下均選擇找出最小k個數。解法1 快速選擇 借助快排是思想,進行區域性的排序。void quick...

螺旋佇列問題解法

看到這個怪圖了嗎?對,就是螺旋佇列!看清以上數字排列的規律,設 1 點的座標是 0,0 x 方向向右為正,y 方向向下為正。例如,7 的座標為 1,1 2 的座標為 1,0 3 的座標為 1,1 程式設計實現輸入任意一點座標 x,y 輸出所對應的數字。finland 某著名通訊裝置公司 2005 年...