求N個數中最大的K個數的幾種方法與實現

來源:互聯網
上載者:User

    某筆試題:記憶體中有一個長數組,有100W條記錄, 每個記錄為一個struct array, sizeof( array ) = 512, 在這個struct裡有一個int型成員變數weight, 現要取得按個weight值從大到小排序的前500個數組單元(求N裡的前K個大的數)

 

直接貼代碼吧,廢話少講(先說方法吧)~~~~解釋在注釋裡:)

 

const static long N = 10000000; 
const static long K = 100;
static long cnt;

struct Elem
{
 Elem()
 {
  weight = rand()%100000;
 }
 long weight;
 //char data[512];
};

void main()
{
 srand(time(NULL));

 //直接用資料會越過程式初始棧大小,所在要在堆裡申請空間~~~運行時可以斷一下點,看一下工作管理員佔用多少記憶體
 Elem  * p = new Elem[N];
 long take = GetTickCount();  
 //time_t   first,   second; 
 //first   =   time(NULL);     /*   Gets   system */

 //方法一
 //先將最大的k個交換到前面,再對其全排
 DirectSearch( p, K );
 QuickSort( p, 0, K - 1 );
 PrintElem( p, K );

 //方法二
 //先用半快排,求出長度為k的區間後,再對其全排
//  PartQuickSort( p, 0, N - 1, K );
//  QuickSort( p, 0, K - 1 );
//  PrintElem( p, K );

 //方法三
 //用一個小根堆儲存這k個數, 遍曆一次即可,每次與第一個比較, 如果更新了就調整
 //HeapK( p, K );
 
 //估計做出以上三種方法的實現,面試官都基本滿意啦,一般軟體公司的offer也是八九不離十了.
 //但是,繼續還有方法...

 //解法四: 如果加上限制條件(1) 所有數為整數 (2) 所有數的變化範圍不大 這樣就可以利用記數排序法的思想
 //Countsort( p, K )
 
 //second   =   time(NULL);   /*   Gets   system   time    again   */
 cout<<"tick="<< GetTickCount() - take <<endl;
 cout<<"count="<< cnt <<endl;

 //cout<<"tick="<< difftime(second,first)<<endl;  
 
 delete p;
}

 

 

下面者是實現~~~

 

//解法一: 直接尋找 O( N*k), 其實當 k < log2(N) 時是可以用的, 因為要完全排序需要 O( n* log2(n) )<br />//但是如果要求k個數排序的話,還要至少加上 k*log2(k)的時間<br />//直接將其交換到前面位置<br />void DirectSearch( Elem * src, int k )<br />{</p><p>long i =0, j, l,idx;</p><p>long max;<br />Elem t;<br />for( l=0; l<K; l++ )<br />{<br />max = src[l].weight;<br />for( j=l; j <N; j++)<br />{<br />if( max < src[j].weight )<br />{<br />max = src[j].weight;<br />idx = j;<br />}</p><p>//計數器<br />++cnt;<br />}</p><p>//交換到前面<br />memcpy( &t, &src[idx], sizeof(Elem) );<br />memcpy( &src[idx], &src[l], sizeof(Elem) );<br />memcpy( &src[l], &t, sizeof(Elem) );</p><p>}</p><p>}</p><p>//解法二: 部分快排</p><p>//前置聲明<br />int PartPartion( Elem * src, int start, int end );</p><p>//部分快排,實現將前K個最大的放在前端(N個無素無序)<br />//完成後前k個最大數將會放在數組的前k的位置,無序<br />void PartQuickSort( Elem * src, int start , int end, int k )<br />{<br />//按照快排的思想,以第一個為標記元素M,大於M的放交換到左邊,小於M放到右邊, 當左分區的要少於N時結束<br />//返回上一次分區的大小<br />//---------------<br />//其實上面的想法,會導致分組過大的,如要在10個裡找出前5個,當計算到m為3時,但前一個可能為8,<br />//這裡就直接全排這個8個元素就會做很我多無用的排序了<br />//最佳化:<br />//(1) 當找到 m < N 時, 繼續在大的分區找出 N - m個<br />//(2)遞迴進行,真到有m == N 時結束<br />//(3)演算法複雜度為O(N* log2(k) )</p><p>//if( k == 0 )<br />//return;</p><p>int m = 0;<br />//m = PartPartion( src, cur , ElemLen - 1 );<br />//if( m <= k )<br />//PartQuickSort( src + m + 1, ElemLen - m -1 , k - m - 1, m + 1);</p><p>if( start < end )<br />{<br />m = PartPartion( src, start , end );</p><p>if( m <= k )<br />PartQuickSort( src, m+1 , end, k - m + 1 );<br />else<br />PartQuickSort( src, start , m - 1, k );</p><p>}</p><p>}<br />//部分快排的分解<br />int PartPartion( Elem * src, int start, int end )<br />{<br />Elem t, mid;</p><p>int i,j;</p><p>i = start;<br />j = end;<br />mid = src[start];</p><p>while( i< j )<br />{<br />while( i<j && src[j].weight <= mid.weight )<br />j--;</p><p>while( i < j && src[i].weight >= mid.weight )<br />i++;</p><p>if( i<j)<br />{<br />//如果Elem結構裡有數組,就不能直接賦值,所以改用memcpy直接複製記憶體<br />memcpy( &t, &src[i], sizeof(Elem) );<br />memcpy( &src[i], &src[j], sizeof(Elem) );<br />memcpy( &src[j], &t, sizeof(Elem) );<br />}<br />}</p><p>memcpy( &t, &src[i], sizeof(Elem) );<br />memcpy( &src[i], &src[start], sizeof(Elem) );<br />memcpy( &src[start], &t, sizeof(Elem) );<br />++cnt;</p><p>return i;<br />}<br />//對前N個元素進行快排<br />void QuickSort( Elem * src, int start , int end )<br />{<br />//完全排序<br />if( start < end )<br />{<br />int m = PartPartion( src, start , end );<br />QuickSort( src, start, m -1 );<br />QuickSort( src, m +1, end );<br />}<br />}</p><p>//解法三: 將一個K個資料的堆,遍曆一次即可,以最小堆形式,這樣只要比較第一個元素F, 如果比F大,就替找F,之後調整堆<br />//同樣演算法複雜度為 O(N* log2(k) ), 而且實現簡單</p><p>//前置聲明<br />bool AdjHeap( int * WArray, int i, int len );<br />void MinHeap( int * WArray, int len );<br />void HeapSort( int * WArray, int len );</p><p>void HeapK( Elem * src, int k )<br />{<br />//所有元素以一個數組表示, 再按堆操作調整<br />//這裡只儲存weight值<br />int * weight = new int[ k ];<br />memset( weight, 0, k );</p><p>long i;<br />int idx = 0;</p><p>//先將前k 個元素的weight加入到堆中<br />for( i=0; i<k; i++ )<br />weight[i] = src[i].weight;</p><p>//調整為小根堆,方便比較<br />MinHeap( weight, k );</p><p>//遍曆一次即可<br />for( i=k; i<N; i++ )<br />{<br />if( weight[0] < src[k].weight )<br />{</p><p>//每置換一次堆頂就要重新調整<br />weight[0] = src[k].weight;<br />AdjHeap( weight, 0, k - 1 );</p><p>}<br />}</p><p>//最後weight數組為前k個最大的元素,如果要排序輸出,那麼要做一次堆排序<br />HeapSort( weight, k );</p><p> for( i=0; i<K; i++ )<br /> cout<< weight[i] << " ";<br /> cout<<endl;</p><p>delete weight;<br />}</p><p>void HeapSort( int * WArray, int len )<br />{<br />int i;<br />int t;<br />for( i=0; i<len ; i++ )<br />{</p><p>t = WArray[0];<br />WArray[0] = WArray[len - i -1];<br />WArray[len - i - 1] = t;</p><p>//當沒有調整時,完成排序<br />AdjHeap( WArray, 0, len - i - 2 );</p><p>}<br />}</p><p>bool AdjHeap( int * WArray, int i, int len )<br />{<br />bool change = false;<br />int j,t;<br />for( j =i*2+1; j <= len; j = j*2+1 )<br />{</p><p>if( j +1 <= len && WArray[j] > WArray[ j +1 ] )<br />j++;</p><p>if( WArray[j] < WArray[(j-1)/2] )<br />{<br />change = true;<br />t = WArray[(j-1)/2];<br />WArray[(j-1)/2] = WArray[j];<br />WArray[j] = t;<br />}</p><p>}</p><p>return change;<br />}</p><p>void MinHeap( int * WArray, int len )<br />{<br />if( len <= 0 )<br />return;</p><p>//根據堆的性質: 左孩子為 2i +1 , 右孩為 2i + 2( 因為從0開始計數 )</p><p>int i;</p><p>//從最後一個有孩子的結點開始,向前調整<br />len--;<br />for( i = (len-1)/2; i >=0; i-- )<br />AdjHeap( WArray, i, len );</p><p>}</p><p>//解法四: 如果加上限制條件(1) 所有數為整數 (2) 所有數的變化範圍不大 這樣就可以利用記數排序法的思想<br />//遍曆一次N, 找出最大值MAX<br />//開一個數組 T[MAX];<br />//再遍曆一次N, 對每個數計數 T[ A[i] ]++; ( 0<= i <= N )<br />//由於是求前K個最大的數,所以就可以從後面起取</p><p>void Countsort( Elem * src, int k )<br />{<br />long i,j;<br />int max;<br />int wt;<br />int * kArray = new int[k];<br />long take = GetTickCount();<br />//遍曆一次N, 找出最大值MAX<br />max = src[0].weight;<br />for( i=0; i<N; i++)<br />{<br />wt = src[i].weight;<br />max < wt ? max = wt:max;<br />}<br />//開一個數組 T[MAX];<br />int * T = new int[ max + 1 ];<br />memset( T, 0, sizeof(int)*(max + 1) );<br />//再遍曆一次N, 對每個數計數 T[ A[i] ]++; ( 0<= i <= N )<br />for( i=0; i<N; i++)<br />{<br />wt = src[i].weight;<br />T[ wt ]++;<br />}</p><p>//由於是求前K個最大的數,所以就可以從後面起取<br />int n = k;<br />int idx = 0;<br />for( i = max; i>0 ; i-- )<br />{<br />//跳過沒有的數<br />if( !T[i] )<br />continue;<br />if( T[i] >= n )<br />{<br />//儲存結果在另一個數組,以免影響計算時間<br />for( j =0; j<n; j++ )<br />{<br />kArray[idx++] = i;<br />//cout<<i<<" ";<br />}<br />break;<br />}<br />else<br />{<br />//輸出這麼多個計數<br />for( j =0; j<T[i]; j++ )<br />{<br />kArray[idx++] = i;<br />//cout<<i<<" ";<br />}<br />n -= T[i];<br />}<br />}<br />//輸出結果<br />for( j =0; j<n; j++ )<br />cout<<kArray[i]<<" ";<br />cout<<endl;<br />cout<<"tick="<< GetTickCount() - take <<endl;<br />delete T;<br />delete kArray;<br />}</p><p>//輸出<br />void PrintElem( Elem * src, int Len )<br />{<br />for( int i =0; i< Len; i++ )<br />cout<<src[i].weight<<" ";<br />cout<<endl;<br />}

   

    呵呵,相信在面試時能詳細說出以上幾鐘演算法的優缺點,並現場寫出一兩個,估計他會當場拍板:我找的就是你!

呼~~~當時就是衰在這個上啦, 所以決定完整的做一遍, 基礎還是很重要的!!大家也加油啦~~~

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.