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

 某笔试题:内存中有一个长数组,有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;
}

下面者是实现~~~

view plaincopy to clipboardprint?
//解法一: 直接查找 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 );  
        else 
            PartQuickSort( 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, 找出最大值MAX  
    max = 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;  

//解法一: 直接查找 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 );
  else
   PartQuickSort( 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, 找出最大值MAX
 max = 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;
}

   

    呵呵,相信在面试时能详细说出以上几钟算法的优缺点,并现场写出一两个,估计他会当场拍板:我找的就是你!

呼~~~当时就是衰在这个上啦, 所以决定完整的做一遍, 基础还是很重要的!!大家也加油啦~~~

 

本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/kikikind/archive/2009/06/22/4288411.aspx

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值