求N个数中最大的K个数的几种方法与实现

来源:互联网 发布:stc52单片机控制浇花 编辑:程序博客网 时间:2024/04/29 00:18
 某笔试题:内存中有一个长数组,有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) )//但是如果要求k个数排序的话,还要至少加上 k*log2(k)的时间//直接将其交换到前面位置void DirectSearch( Elem * src, int k ){long i =0, j, l,idx;long max;Elem t;for( l=0; l<K; l++ ){max = src[l].weight;for( j=l; j <N; j++){if( max < src[j].weight ){max = src[j].weight;idx = j;}//计数器++cnt;}//交换到前面memcpy( &t, &src[idx], sizeof(Elem) );memcpy( &src[idx],  &src[l], sizeof(Elem) );memcpy( &src[l],  &t, sizeof(Elem) );}}//解法二: 部分快排//前置声明int PartPartion( Elem * src, int start, int end );//部分快排,实现将前K个最大的放在前端(N个无素无序)//完成后前k个最大数将会放在数组的前k的位置,无序void PartQuickSort( Elem * src, int start , int end, int k  ){//按照快排的思想,以第一个为标记元素M,大于M的放交换到左边,小于M放到右边, 当左分区的要少于N时结束//返回上一次分区的大小//---------------//其实上面的想法,会导致分组过大的,如要在10个里找出前5个,当计算到m为3时,但前一个可能为8,//这里就直接全排这个8个元素就会做很我多无用的排序了//优化: //(1) 当找到 m < N 时, 继续在大的分区找出 N - m个//(2)递归进行,真到有m == N 时结束 //(3)算法复杂度为O(N* log2(k) )//if( k == 0 )//return;int m = 0;//m = PartPartion( src, cur , ElemLen - 1 );//if( m <= k )//PartQuickSort( src + m + 1, ElemLen - m -1 , k - m - 1, m + 1);if( start < end ){m = PartPartion( src, start , end );if( m <= k )PartQuickSort( src, m+1 , end, k - m + 1 );elsePartQuickSort( src, start , m - 1, k );}}//部分快排的分解int PartPartion( Elem * src, int start, int end ){Elem t, mid;int i,j;i = start;j = end;mid = src[start];while( i< j ){while( i<j && src[j].weight <= mid.weight )j--;while( i < j && src[i].weight >= mid.weight ) i++;if( i<j){//如果Elem结构里有数组,就不能直接赋值,所以改用memcpy直接复制内存memcpy( &t, &src[i], sizeof(Elem) );memcpy( &src[i],  &src[j], sizeof(Elem) );memcpy( &src[j],  &t, sizeof(Elem) );}}memcpy( &t, &src[i], sizeof(Elem) );memcpy( &src[i],  &src[start], sizeof(Elem) );memcpy( &src[start],  &t, sizeof(Elem) );++cnt;return i;}//对前N个元素进行快排void QuickSort( Elem * src, int start , int end  ){//完全排序if( start < end ){int m = PartPartion( src, start , end );QuickSort( src, start, m -1 );QuickSort( src, m +1, end );}}//解法三: 将一个K个数据的堆,遍历一次即可,以最小堆形式,这样只要比较第一个元素F, 如果比F大,就替找F,之后调整堆//同样算法复杂度为 O(N* log2(k) ), 而且实现简单//前置声明bool AdjHeap( int * WArray, int i, int len );void MinHeap( int * WArray, int len );void HeapSort( int * WArray, int len );void HeapK( Elem * src, int k ){//所有元素以一个数组表示, 再按堆操作调整//这里只保存weight值int * weight = new int[ k ];memset( weight, 0, k );long i;int idx = 0;//先将前k 个元素的weight加入到堆中for( i=0; i<k; i++ )weight[i] = src[i].weight;//调整为小根堆,方便比较MinHeap( weight, k );//遍历一次即可for( i=k; i<N; i++ ){if( weight[0] < src[k].weight ){//每置换一次堆顶就要重新调整weight[0] = src[k].weight;AdjHeap( weight, 0, k - 1 );}}//最后weight数组为前k个最大的元素,如果要排序输出,那么要做一次堆排序HeapSort( weight, k  ); for( i=0; i<K; i++ ) cout<< weight[i] << " "; cout<<endl;delete weight;}void HeapSort( int * WArray, int len ){int i;int t;for( i=0; i<len ; i++ ){t = WArray[0];WArray[0] = WArray[len - i -1];WArray[len - i - 1] = t;//当没有调整时,完成排序AdjHeap( WArray, 0, len - i - 2 );}}bool AdjHeap( int * WArray, int i, int len ){bool change = false;int j,t;for( j =i*2+1; j <= len; j = j*2+1 ){if( j +1 <= len && WArray[j] > WArray[ j +1 ] )j++;if( WArray[j] < WArray[(j-1)/2] ){change = true;t = WArray[(j-1)/2];WArray[(j-1)/2] = WArray[j];WArray[j] = t;}}return change;}void MinHeap( int * WArray, int len ){if( len <= 0 )return;//根据堆的性质: 左孩子为 2i +1 , 右孩为 2i + 2( 因为从0开始计数 )int i;//从最后一个有孩子的结点开始,向前调整len--;for( i = (len-1)/2; i >=0; i-- )AdjHeap( WArray, i, len );}//解法四: 如果加上限制条件(1) 所有数为整数 (2) 所有数的变化范围不大 这样就可以利用记数排序法的思想//遍历一次N, 找出最大值MAX//开一个数组 T[MAX]; //再遍历一次N, 对每个数计数 T[ A[i] ]++; ( 0<= i <= N ) //由于是求前K个最大的数,所以就可以从后面起取void Countsort( Elem * src, int k ){long i,j;int max;int wt;int * kArray = new int[k];long take = GetTickCount();  //遍历一次N, 找出最大值MAXmax = src[0].weight;for( i=0; i<N; i++){wt = src[i].weight;max < wt  ? max = wt:max;}//开一个数组 T[MAX]; int * T = new int[ max + 1 ];memset( T, 0, sizeof(int)*(max + 1) );//再遍历一次N, 对每个数计数 T[ A[i] ]++; ( 0<= i <= N ) for( i=0; i<N; i++){wt = src[i].weight;T[ wt ]++;}//由于是求前K个最大的数,所以就可以从后面起取int n = k;int idx = 0;for( i = max; i>0 ; i-- ){//跳过没有的数if( !T[i] )continue;if( T[i] >= n ){//保存结果在另一个数组,以免影响计算时间for( j =0; j<n; j++ ){kArray[idx++] = i;//cout<<i<<" ";}break;}else{//输出这么多个计数for( j =0; j<T[i]; j++ ){kArray[idx++] = i;//cout<<i<<" ";}n -= T[i];}}//输出结果for( j =0; j<n; j++ )cout<<kArray[i]<<" ";cout<<endl;cout<<"tick="<< GetTickCount() - take <<endl;delete T;delete kArray;}//输出void PrintElem( Elem * src, int Len ){for( int i =0; i< Len; i++ )cout<<src[i].weight<<" ";cout<<endl;}