手撕演算法系列 2 top k問題

2021-08-20 04:46:46 字數 2551 閱讀 8638

這道題也是很經典的面試題了,因為很多網際網路公司要處理海量資料,從海量資料中篩選第k大(小)的資料成為了很常見的問題,這道題也因為解法眾多而一直受到熱議。下面假定問題是要從n個不同大小的資料尋找第k大的元素,即有k-1個元素大於它。

(1)解法一——簡單粗暴排序

這個解法不用多說了,如果使用基於比較的排序,則平均時間複雜度為o(nlogn),如果n很大的話,這個時間複雜度是難以接受的,而且問題只需要尋找k個最大的元素,而這個解法將所有元素排序了一遍,相當不划算。

(2)解法二——每次剔除乙個最大的元素,直至第k個

這個解法也很容易實現,尋找陣列中最大的元素的時間複雜度是線性的,因此這個演算法的時間複雜度為o(nk),如果k<(3)解法三——用最小堆實現

top-k問題可以理解為尋找k個最大的元素裡面最小的乙個,因此可以利用堆的性質,即最小堆的root結點的data域是堆中最小的。先建立乙個結點數固定為k的最小堆,遍歷整個陣列的元素,將其與堆的根節點值進行比較,如果遇到比根節點值更大的元素,則更新根節點值為該值,並將堆重新排序;如果遇到的值比根節點值小,則直接pass掉。這樣遍歷完整個陣列後,根節點的值便是第k大的值了。這個演算法的視覺化執行過程可以在ben frederickson的個人部落格中找到,很形象。

由於堆重排的時間複雜度為o(logk),因此整個演算法的時間複雜度為o(nlogk),比解法二優化了不少,並且這個解法的優點不僅可以找到第k大的元素,還能找出前k大的k個元素。

c語言實現:

/*

尋找n個不同資料中第k大的元素

介面:int top_k(int *a,int n,int k);

輸入:乙個由n個不同元素組成的陣列a,a的長度n以及k的值

輸出:陣列a中第k大的元素

*/#include#define max_size 10001

int min_heap[max_size]=; //假定陣列中的數都是正數

int heap_count=0;

void reheap(int k) //更新根節點的數值後,調整使其符合最小堆

return min_heap[0];

}int main(void)

;        //測試樣例,最後函式返回6,正確

printf("%d\n",top_k(array,10,5));

return 0;

}

(4)解法四——基於快速排序劃分思想的二分法(bfprt演算法)

該演算法的思路如下:

1)利用一定的手段選擇出乙個主元(pivot),如果圖方便,可以選擇下標下界的元素作主元,但是主元的位置越靠近中間,總體的時間複雜度就越優,一般認為效能比較優秀的演算法是median of median取出來的中位數(詳情可見wiki詞條),具體來說,先將n個元素劃分為5個一組,共floor(n/5)個組,剩餘不足5個的直接捨棄掉,計算每組中的中位數,並將所有的中位數繼續劃分為5個一組,遞迴求解,直到只剩最後乙個中位數,該中位數的下標就可以作為主元下標了;

2)選擇出主元後,利用快速排序的思想,將大於主元的元素全部劃分到主元的左邊,小於主元的元素劃分到其右邊;

3)劃分完畢後,如果主元的下標恰好為k-1,則說明主元就是第k大的元素;如果主元的下標小於k-1,說明第k大的元素在主元的右邊,對其右邊遞迴求解;反之,對其左邊遞迴求解。

綜合以上幾步,我們需要實現的功能有:求出長度為5的陣列的中位數、求出長度為n的陣列的主元位置、劃分陣列以及最終的排序這4個功能,c語言實現如下:

#include#define max_size 10001

int insertsort(int array, int left, int right); //插入排序,返回中位數下標

int getpivotindex(int array, int left, int right); //返回中位數的中位數下標

int partition(int array, int left, int right, int pivot_index); //利用中位數的中位數的下標進行劃分,返回分界線下標

int bfprt(int array, int left, int right, int k);

void swap(int *a, int i, int j)

int get_median(int *a, int low, int high)

a[j+1] = temp;

} return low + (high-low)/2;

} int get_index(int *a, int low, int high)

return bfprt(a, low, sub_high, ((sub_high - low + 1) / 2) + 1);

}int partition(int *a, int low, int high, int index)

swap(a, index, high); //最後把基準換回來

return index;

}int bfprt(int *a, int low, int high, int k)

《演算法》系列 大白話聊分治 回溯,手撕八皇后

分治就是分而治之,即把乙個問題分解成很多個子問題,且這些問題與原問題結構相似,然後遞迴解決子問題,最後合併子問題的結果,得到原來問題的結果。分治解題三個步驟 分解 將問題分解成與原問題結構相似的子問題 解決 遞迴求解各個子問題,當子問題足夠小,直接返回結果 問題分解到足夠小,分解終止條件 合併 將子...

演算法系列 N皇后問題

常規n皇后解決問題過程 一 問題描述 運用回溯法解題通常包含以下三個步驟 1 針對所給問題,定義問題的解空間 2 確定易於搜尋的解空間結構 3 以深度優先的方式搜尋解空間,並且在搜尋過程中用剪枝函式避免無效搜尋 通過上述的基本思路,我們可以將問題描述為 x j 表示乙個解的空間,j表示行數,裡面的值...

演算法系列 約瑟夫斯問題

約瑟夫斯問題 有時也稱為約瑟夫斯置換 是乙個出現在電腦科學 和數學中的問題。在計算機程式設計 的演算法中,類似問題又稱為約瑟夫環。有個囚犯站成乙個圓圈 準備處決。首先從乙個人開始,越過 個人 因為第乙個人已經被越過 並殺掉第k個人。接著,再越過 個人,並殺掉第k個人。這個過程沿著圓圈一直進行,直到最...