《程式設計之美》 尋找最大的K個數

2021-07-07 09:10:24 字數 4300 閱讀 5662

題目:

n個無序的數(可能數目非常大),選出其中最大的k個數。

分析與解法:

【解法一】

對n個數進行排序,然後選出最大的k個數。可以使用快速排序或堆排序,時間複雜度為o(nlogn)

因為這裡n的數目可能非常大,即n>>k,而前n-k個數可以不進行排序,使用可以部分排序的演算法,如選擇排序或交換排序,時間複雜度為o(nk)

【解法二】

當n的數值很大的時候,即n>>k。可以使用快速排序中的partition函式, 將數分為兩組。

(1)分為兩個組,sa和sb。

(2)若sa組的個數大於k,則繼續在sa分組中找取最大的k個數字 。

(3)若sa組中的數字小於k ,其個數為t,則繼續在sb中找取 k-t個數字 。

時間複雜度為o(nlogk)

**:

#include 

using

namespace

std ;

const

int n = 8 ;

const

int k = 4 ;

int partition(int a ,int low , int high)

j++ ;

} //最後處理a[high]

swap(a[i+1] , a[high]) ;

return i + 1;

} int findk(int a , int low , int high , int k)

} int main()

; findk(a , 0 , n - 1 , k) ;

for(int i = 0 ; i < k ; i++)

cout

<"pause") ;

return

0 ;

}

【解法三】

該問題的實質是尋找最大的k個數中最小的那個,也就是第k大的數p,可以先找出n個數中最大的和最小的元素的值,然後使用二分法進行搜尋找到p,再根據p找到其他大於它的k-1個數。其時間複雜度是o(n)+o(nlogn)+o(n),近似後為o(nlogn)

**:

#include using namespace std ;   

const int n = 8 ;

const int k = 4 ;

/* 利用二分的方法求取top k問題。

首先查詢 max 和 min,然後計算出 mid = (max + min) / 2

該演算法的實質是尋找最大的k個數中最小的乙個。

*/int find(int * a , int x) //查詢出大於或者等於x的元素個數

return sum ;

} int getk(int * a , int

max , int

min) //最終max min之間只會存在乙個或者多個相同的數字

return

min ;

} int main()

;

int x = getk(a , 554 , 2);

cout << x << " ";

for(int i = 0; i < n; i++)

return

0 ;

}

【解法四】

當n的數值很大的時候,即n>>k。從n個數中任取k個數建立乙個有k個節點的小頂堆,每次從剩下n-k個數中取出乙個數,與堆頂元素進行比較,若小於等於堆頂元素則捨棄,若大於等於堆頂,則將堆頂元素更新為該元素並重新調整堆。維護堆的時間複雜度是o(logk)。所以總的時間複雜度是o(nlogk)

**:

#include 

using

namespace

std;

void buildminheap(int *parray, int k);

void adjustheap(int *parray, int rootindex, int heapsize);

int main()

; int k = 5 ;

//建乙個k個元素大小的最小堆

buildminheap(a, k);

//從第k個元素開始掃瞄,看有沒有比根節點更大的節點,若有則替換,並更新堆;若沒有比根節點大則掃瞄下乙個元素,直到陣列結束

for (int i = k; i < sizeof(a) / sizeof(int); i++)

}//列印出前k大的數,沒有排序。

for (int i = 0; i < k; i++)

system("pause");

} //建乙個k個元素大小的最小堆

void buildminheap(int *parray, int k)

}void adjustheap (int *parray, int rootindex, int heapsize)

if ((leftindex < heapsize) && (rightindex >= heapsize) &&

(parray[leftindex] < parray[rootindex]))

if ((rightindex < heapsize) && (parray[rightindex] <

parray[leftindex]) && (parray[rightindex] <

parray[rootindex]))

if (minindex != rootindex)

}

【解法五】

n個數都是正整數,且取值範圍不大的時候。可以使用空間換時間的方法,使用乙個陣列記錄每個元素出現的次數,陣列長度為maxn,為n個數中的最大元素的值,然後找出最大的k個數。時間複雜度為o(n)+o(maxn),近似為o(n)

**:

#include 

using namespace std;

int findmaxn(int *parray, int len);

int main()

; int k = 5;

int maxn = findmaxn(a, sizeof(a) / sizeof(int));

//申請乙個count陣列,記錄每乙個數出現的次數

int *count = new

int[maxn + 1]();

for (int i = 0; i < sizeof(a) / sizeof(int); i++)

intindex = maxn;

int sumcount = 0;

for (;index >= 0; index--)

}//列印出最大的k個數

for (int i = maxn; i >= index; i--)

}system("pause");

} //找出乙個陣列中最大的值

int findmaxn(int *parray, int len)

}return maxn;

}

擴充套件問題:

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)。

2.如果是找第k到第m大的數呢?

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

分析與解法:

1.除了解法五,其他四種方法都可以。還可以利用解法五的思想使用stl中的map儲存每個數出現的次數,之後從大到小掃瞄出k個最大的數,時間複雜度為o(nlogn),空間複雜度為o(n),因為stl中map是由紅黑樹實現的,每次插入的時間複雜度為o(logn)。

2.維護乙個m個節點的小頂堆,然後遍歷找出比第m大的元素小的m-k個元素。

3.4.5.解法見參考博文。

文章及**參考以下博文:

程式設計之美 尋找最大的K個數

有很多個無序數,我們姑且假定他們各不相等,怎麼挑選出其中最大的若干個數呢?如果這個資料量很大,比如1億個,如果所存資料是浮點型呢?我們該怎麼處理呢?分兩部分,第一部分是我個人的解答,第二部分是書上的解答 第一部分 1,如果這個問題裡的資料都是整數,這個問題利用hash對映應該很簡單,就是在開闢乙個陣...

程式設計之美 尋找最大的K個數

有很多個無序數,我們姑且假定他們各不相等,怎麼挑選出其中最大的若干個數呢?如果這個資料量很大,比如1億個,如果所存資料是浮點型呢?我們該怎麼處理呢?分兩部分,第一部分是我個人的解答,第二部分是書上的解答 第一部分 1,如果這個問題裡的資料都是整數,這個問題利用hash對映應該很簡單,就是在開闢乙個陣...

程式設計之美 尋找最大的k個數

有很多無序的數,我們姑且假定它們各不相等,怎麼選出其中最大的若干個數呢?idea 1 先用快速排序或者堆排序進行排序,然後取出最大的k個數,時間複雜度為o nlogn o k o nlogn idea 2 進行k趟最大冒泡或者k次大頂堆的輸出,時間複雜度為o n k 根據k與logn的大小比較,選取...