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

 转自http://blog.chinaunix.net/uid-24203478-id-3195251.html

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

下面者是实现~~~

  1. //解法一: 直接查找 O( N*k), 其实当 k < log2(N) 时是可以用的, 因为要完全排序需要 O( n* log2(n) ) 
  2. //但是如果要求k个数排序的话,还要至少加上 k*log2(k)的时间 
  3. //直接将其交换到前面位置 
  4. void DirectSearch( Elem * src, int k ) 
  5.  
  6.     long i =0, j, l,idx; 
  7.  
  8.     long max; 
  9.     Elem t; 
  10.     for( l=0; l<K; l++ ) 
  11.     { 
  12.         max = src[l].weight; 
  13.         for( j=l; j <N; j++) 
  14.         { 
  15.             if( max < src[j].weight ) 
  16.             { 
  17.                 max = src[j].weight; 
  18.                 idx = j; 
  19.             } 
  20.  
  21.             //计数器 
  22.             ++cnt; 
  23.         } 
  24.  
  25.         //交换到前面 
  26.         memcpy( &t, &src[idx], sizeof(Elem) ); 
  27.         memcpy( &src[idx],  &src[l], sizeof(Elem) ); 
  28.         memcpy( &src[l],  &t, sizeof(Elem) ); 
  29.  
  30.     } 
  31.  
  32.  
  33. //解法二: 部分快排 
  34.  
  35. //前置声明 
  36. int PartPartion( Elem * src, int start, int end ); 
  37.  
  38. //部分快排,实现将前K个最大的放在前端(N个无素无序) 
  39. //完成后前k个最大数将会放在数组的前k的位置,无序 
  40. void PartQuickSort( Elem * src, int start , int end, int k  ) 
  41.     //按照快排的思想,以第一个为标记元素M,大于M的放交换到左边,小于M放到右边, 当左分区的要少于N时结束 
  42.     //返回上一次分区的大小 
  43.     //--------------- 
  44.     //其实上面的想法,会导致分组过大的,如要在10个里找出前5个,当计算到m为3时,但前一个可能为8, 
  45.     //这里就直接全排这个8个元素就会做很我多无用的排序了 
  46.     //优化:  
  47.     //(1) 当找到 m < N 时, 继续在大的分区找出 N - m个 
  48.     //(2)递归进行,真到有m == N 时结束  
  49.     //(3)算法复杂度为O(N* log2(k) ) 
  50.  
  51.     //if( k == 0 ) 
  52.         //return; 
  53.  
  54.     int m = 0; 
  55.     //m = PartPartion( src, cur , ElemLen - 1 ); 
  56.     //if( m <= k ) 
  57.         //PartQuickSort( src + m + 1, ElemLen - m -1 , k - m - 1, m + 1); 
  58.  
  59.     if( start < end ) 
  60.     { 
  61.         m = PartPartion( src, start , end ); 
  62.  
  63.         if( m <= k ) 
  64.             PartQuickSort( src, m+1 , end, k - m + 1 ); 
  65.         else 
  66.             PartQuickSort( src, start , m - 1, k ); 
  67.  
  68.     } 
  69.  
  70. //部分快排的分解 
  71. int PartPartion( Elem * src, int start, int end ) 
  72.     Elem t, mid; 
  73.  
  74.     int i,j; 
  75.      
  76.     i = start; 
  77.     j = end; 
  78.     mid = src[start]; 
  79.      
  80.     while( i< j ) 
  81.     { 
  82.         while( i<j && src[j].weight <= mid.weight ) 
  83.             j--; 
  84.  
  85.         while( i < j && src[i].weight >= mid.weight )  
  86.             i++; 
  87.  
  88.         if( i<j) 
  89.         { 
  90.             //如果Elem结构里有数组,就不能直接赋值,所以改用memcpy直接复制内存 
  91.             memcpy( &t, &src[i], sizeof(Elem) ); 
  92.             memcpy( &src[i],  &src[j], sizeof(Elem) ); 
  93.             memcpy( &src[j],  &t, sizeof(Elem) ); 
  94.         } 
  95.     } 
  96.  
  97.     memcpy( &t, &src[i], sizeof(Elem) ); 
  98.     memcpy( &src[i],  &src[start], sizeof(Elem) ); 
  99.     memcpy( &src[start],  &t, sizeof(Elem) ); 
  100.     ++cnt; 
  101.  
  102.     return i; 
  103. //对前N个元素进行快排 
  104. void QuickSort( Elem * src, int start , int end  ) 
  105.     //完全排序 
  106.     if( start < end ) 
  107.     { 
  108.         int m = PartPartion( src, start , end ); 
  109.         QuickSort( src, start, m -1 ); 
  110.         QuickSort( src, m +1, end ); 
  111.     } 
  112.  
  113. //解法三: 将一个K个数据的堆,遍历一次即可,以最小堆形式,这样只要比较第一个元素F, 如果比F大,就替找F,之后调整堆 
  114. //同样算法复杂度为 O(N* log2(k) ), 而且实现简单 
  115.  
  116. //前置声明 
  117. bool AdjHeap( int * WArray, int i, int len ); 
  118. void MinHeap( int * WArray, int len ); 
  119. void HeapSort( int * WArray, int len ); 
  120.  
  121. void HeapK( Elem * src, int k ) 
  122.     //所有元素以一个数组表示, 再按堆操作调整 
  123.     //这里只保存weight值 
  124.     int * weight = new int[ k ]; 
  125.     memset( weight, 0, k ); 
  126.  
  127.     long i; 
  128.     int idx = 0; 
  129.  
  130.     //先将前k 个元素的weight加入到堆中 
  131.     for( i=0; i<k; i++ ) 
  132.         weight[i] = src[i].weight; 
  133.  
  134.     //调整为小根堆,方便比较 
  135.     MinHeap( weight, k ); 
  136.  
  137.  
  138.     //遍历一次即可 
  139.     for( i=k; i<N; i++ ) 
  140.     { 
  141.         if( weight[0] < src[k].weight ) 
  142.         { 
  143.  
  144.             //每置换一次堆顶就要重新调整 
  145.             weight[0] = src[k].weight; 
  146.             AdjHeap( weight, 0, k - 1 ); 
  147.  
  148.         } 
  149.     } 
  150.  
  151.  
  152.     //最后weight数组为前k个最大的元素,如果要排序输出,那么要做一次堆排序 
  153.     HeapSort( weight, k  ); 
  154.  
  155.      for( i=0; i<K; i++ ) 
  156.         cout<< weight[i] << " "
  157.      cout<<endl; 
  158.  
  159.  
  160.  
  161.     delete weight; 
  162.  
  163. void HeapSort( int * WArray, int len ) 
  164.     int i; 
  165.     int t; 
  166.     for( i=0; i<len ; i++ ) 
  167.     { 
  168.  
  169.         t = WArray[0]; 
  170.         WArray[0] = WArray[len - i -1]; 
  171.         WArray[len - i - 1] = t; 
  172.          
  173.         //当没有调整时,完成排序 
  174.         AdjHeap( WArray, 0, len - i - 2 ); 
  175.              
  176.     } 
  177.  
  178.  
  179. bool AdjHeap( int * WArray, int i, int len ) 
  180.     bool change = false
  181.     int j,t; 
  182.     for( j =i*2+1; j <= len; j = j*2+1 ) 
  183.     { 
  184.  
  185.         if( j +1 <= len && WArray[j] > WArray[ j +1 ] ) 
  186.             j++; 
  187.  
  188.         if( WArray[j] < WArray[(j-1)/2] ) 
  189.         { 
  190.             change = true
  191.             t = WArray[(j-1)/2]; 
  192.             WArray[(j-1)/2] = WArray[j]; 
  193.             WArray[j] = t; 
  194.         } 
  195.          
  196.     } 
  197.      
  198.     return change; 
  199.  
  200. void MinHeap( int * WArray, int len ) 
  201.     if( len <= 0 ) 
  202.         return
  203.      
  204.     //根据堆的性质: 左孩子为 2i +1 , 右孩为 2i + 2( 因为从0开始计数 ) 
  205.      
  206.     int i; 
  207.      
  208.      
  209.     //从最后一个有孩子的结点开始,向前调整 
  210.     len--; 
  211.     for( i = (len-1)/2; i >=0; i-- ) 
  212.         AdjHeap( WArray, i, len ); 
  213.      
  214.  
  215.  
  216. //解法四: 如果加上限制条件(1) 所有数为整数 (2) 所有数的变化范围不大 这样就可以利用记数排序法的思想 
  217. //遍历一次N, 找出最大值MAX 
  218. //开一个数组 T[MAX];  
  219. //再遍历一次N, 对每个数计数 T[ A[i] ]++; ( 0<= i <= N )  
  220. //由于是求前K个最大的数,所以就可以从后面起取 
  221.  
  222. void Countsort( Elem * src, int k ) 
  223.     long i,j; 
  224.     int max; 
  225.     int wt; 
  226.     int * kArray = new int[k]; 
  227.     long take = GetTickCount();   
  228.     //遍历一次N, 找出最大值MAX 
  229.     max = src[0].weight; 
  230.     for( i=0; i<N; i++) 
  231.     { 
  232.         wt = src[i].weight; 
  233.         max < wt  ? max = wt:max; 
  234.     } 
  235.     //开一个数组 T[MAX];  
  236.     int * T = new int[ max + 1 ]; 
  237.     memset( T, 0, sizeof(int)*(max + 1) ); 
  238.     //再遍历一次N, 对每个数计数 T[ A[i] ]++; ( 0<= i <= N )  
  239.     for( i=0; i<N; i++) 
  240.     { 
  241.         wt = src[i].weight; 
  242.         T[ wt ]++; 
  243.     } 
  244.      
  245.     //由于是求前K个最大的数,所以就可以从后面起取 
  246.     int n = k; 
  247.     int idx = 0; 
  248.     for( i = max; i>0 ; i-- ) 
  249.     { 
  250.         //跳过没有的数 
  251.         if( !T[i] ) 
  252.             continue
  253.         if( T[i] >= n ) 
  254.         { 
  255.             //保存结果在另一个数组,以免影响计算时间 
  256.             for( j =0; j<n; j++ ) 
  257.             { 
  258.                 kArray[idx++] = i; 
  259.                 //cout<<i<<" "; 
  260.             } 
  261.             break
  262.         } 
  263.         else 
  264.         { 
  265.             //输出这么多个计数 
  266.             for( j =0; j<T[i]; j++ ) 
  267.             { 
  268.                 kArray[idx++] = i; 
  269.                 //cout<<i<<" "; 
  270.             } 
  271.             n -= T[i]; 
  272.         } 
  273.     } 
  274.     //输出结果 
  275.     for( j =0; j<n; j++ ) 
  276.         cout<<kArray[i]<<" "
  277.     cout<<endl; 
  278.     cout<<"tick="<< GetTickCount() - take <<endl; 
  279.     delete T; 
  280.     delete kArray; 
  281.  
  282. //输出 
  283. void PrintElem( Elem * src, int Len ) 
  284.     for( int i =0; i< Len; i++ ) 
  285.         cout<<src[i].weight<<" "
  286.     cout<<endl; 

   

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值