【算法总结-top K】堆--查找最小(大)的k个元素

top K问题是一个经典的问题。

    该问题描述为:输入n个整数,输出其中最小的k个元素,例如,输入  1,2,3,4,5,6,7,8 那么最小的4个元素就是1,2,3,4.

    除了这个,top K问题还指:常遇到的一类问题是,在海量数据中找出出现频率最高的前K个数,或者从海量数据中找出最大的前K个数,这类问题通常称为“top K”问题,如:在搜索引擎中,统计搜索最热门的10个查询词;在歌曲库中统计下载率最高的前10首歌等等。

    说到top K(第一类)问题,脑袋中经常闪现的两个概念是:快速排序和堆。为什么是这两个概念呢?原因有:

    1.快速排序的思路:

       给定一个枢轴元素,可以将数组按照这个元素分为两个部分。这个思路对于top K问题有什么作用?答案就是,根据partition的结果(返回的是枢轴的索引),可以轻松得到元素的个数。根据这个数字与K的关系递归划分,最后一定可以得出前面元素个数为k个的划分。

    该思路的实现部分可见:http://blog.csdn.net/ohmygirl/article/details/7846544  快速排序求数组的第K个元素。

    2.堆。

      堆其实是一棵完全二叉树,堆对于两类问题有着很好的解决方案:a.排序问题:由于堆是一棵完全二叉树,所以采用堆堆n元数组进行排序,时间复杂度不会超过O(nlgn),而且只需要几个额外的空间。b.优先级队列。通过插入新元素和调整堆结构来维护堆的性质,每个操作所需要的时间都是O(lgn).

    堆的常见实现是采用一个大小为n的数组存储元素,并且0号单元舍弃不用。对堆中的元素按照层次从上到下,从左到右的顺序依次编号。那么对于一个编号为i的元素:

[plain]  view plain copy print ?
  1. a:如果左孩子存在,那么左孩子的编号为2i  
  2.   
  3. b:如果右孩子存在,那么右孩子的编号为2*i + 1  
  4.   
  5. c:如果有父节点,那么父节点的编号为 i/2  
  6.   
  7. d:节点为叶节点的条件是左孩子且右孩子都为空,为空节点的条件是i<1或者i>n  

    堆的设计对于处理top K问题十分方便。首先设置一个大小为K的堆(如果求最大top K,那么用最小堆,如果求最小top K,那么用最大堆),然后扫描数组。并将数组的每个元素与堆的根比较,符合条件的就插入堆中,同时调整堆使之符合堆的特性,扫描完成后,堆中保留的元素就是最终的结果。说到调整堆,不得不提的是调整的算法,分为两类:

 向下调整(shiftdown) 和 向上调整(shiftup)。

    以最小堆为例:

    向上调整算法 对应的代码如下:

  1. void shiftUp(int *heap,int n){  
  2.     int i = n;  
  3.     for(;;){  
  4.         if(i == 1){  
  5.             break;  
  6.         }  
  7.         int p = i/2;  
  8.         if(heap[p] <= heap[i]){  
  9.             break;  
  10.         }  
  11.         swap(&heap[p],&heap[i]);  
  12.         i = p;  
  13.     }  
  14. }  

    向下调整对应的代码如下:

  1. void shiftDown(int * heap,int n){  
  2.     int i = 1;  
  3.     for(;;){  
  4.         int c = 2*i;  
  5.         if(c > n){  
  6.             break;  
  7.         }  
  8.         if(c+1 <= n){  
  9.             if(heap[c+1] <= heap[c]){  
  10.                 c++;  
  11.             }  
  12.         }  
  13.         if(heap[i] <= heap[c]){  
  14.             break;  
  15.         }  
  16.         swap(&heap[c],&heap[i]);  
  17.         i = c;  
  18.     }  
  19. }  
    有了堆的基本操作,top K问题就有了一个基础(当然也可以完全不用堆解决top K问题)。以最小top K问题为例(此时需要建立大小为k的最大堆),top K的求解过程是:扫描原数组,将数组的前K个元素扔到堆中,调整使之保持堆的特性。对于k之后的元素,如果比堆顶元素小,那么替换堆顶元素并调整堆,扫描是数组完成后,堆中保存的元素就是最终的结果。

    进一步思考,对于海量数据的处理,top K问题如何实现呢,当然堆算法还是可行的。有没有其他的思路呢。

    关于海量数据的处理,推荐july的博客:http://blog.csdn.net/v_july_v/article/details/7382693 如何秒杀99% 的海量数据处理题

    另外一个可以参考的博客:http://dongxicheng.org/big-data/select-ten-from-billions/

    最近在研究hadoop,所以我的想法是,用hadoop的MapReduce算法实现top K问题,是不是效率更高一些,毕竟,hadoop在海量数据处理,并行计算方面还是蛮有优势的。

MapReduce的思路也很简单。所以编码的话,只需要定义任务类,然后再定义内部的Mapper和Reducer静态类就可以了。

转载一段mapReduce的top K代码(代码未经测试,原文地址:http://www.linuxidc.com/Linux/2012-05/60234.htm):

[java]  view plain copy print ?
  1.  package jtlyuan.csdn;    
  2.  import java.io.IOException;    
  3.  import org.apache.Hadoop.conf.Configuration;    
  4.  import org.apache.Hadoop.conf.Configured;    
  5.  import org.apache.Hadoop.fs.Path;    
  6.  import org.apache.Hadoop.io.IntWritable;    
  7.  import org.apache.Hadoop.io.LongWritable;    
  8.  import org.apache.Hadoop.io.Text;    
  9.  import org.apache.Hadoop.mapreduce.Job;    
  10.  import org.apache.Hadoop.mapreduce.Mapper;    
  11.  import org.apache.Hadoop.mapreduce.Reducer;    
  12.  import org.apache.Hadoop.mapreduce.lib.input.FileInputFormat;    
  13.  import org.apache.Hadoop.mapreduce.lib.input.TextInputFormat;    
  14.  import org.apache.Hadoop.mapreduce.lib.output.FileOutputFormat;    
  15.  import org.apache.Hadoop.mapreduce.lib.output.TextOutputFormat;    
  16.  import org.apache.Hadoop.util.Tool;    
  17.  import org.apache.Hadoop.util.ToolRunner;    
  18.  //利用MapReduce求最大值海量数据中的K个数     
  19.  public class TopKNum extends Configured implements Tool {    
  20.  public static class MapClass extends Mapper<LongWritable, Text, IntWritable, IntWritable> {    
  21.     public static final int K = 100;    
  22.     private int[] top = new int[K];    
  23.     public void map(LongWritable key, Text value, Context context)    
  24.         throws IOException, InterruptedException {    
  25.           
  26.         String[] str = value.toString().split(",", -2);    
  27.         try {// 对于非数字字符我们忽略掉     
  28.                 int temp = Integer.parseInt(str[8]);    
  29.                 add(temp);    
  30.         } catch (NumberFormatException e) {   
  31.             //  
  32.         }    
  33.     }    
  34.     private void add(int temp) {//实现插入     
  35.         if(temp>top[0]){    
  36.             top[0]=temp;    
  37.             int i=0;    
  38.             for(;i<99&&temp>top[i+1];i++){    
  39.                 top[i]=top[i+1];    
  40.             }    
  41.             top[i]=temp;    
  42.         }    
  43.     }    
  44.       
  45.     @Override    
  46.     protected void cleanup(Context context) throws IOException,  InterruptedException {    
  47.         for(int i=0;i<100;i++){    
  48.             context.write(new IntWritable(top[i]), new IntWritable(top[i]));    
  49.         }    
  50.     }    
  51.  }    
  52.    
  53.  public static class Reduce extends Reducer<IntWritable, IntWritable, IntWritable, IntWritable> {    
  54.     public static final int K = 100;    
  55.     private int[] top = new int[K];    
  56.     public void reduce(IntWritable key, Iterable<IntWritable> values, Context context)    
  57.             throws IOException, InterruptedException {    
  58.         for (IntWritable val : values) {    
  59.             add(val.get());    
  60.         }    
  61.     }    
  62.  private void add(int temp) {//实现插入  
  63.     if(temp>top[0]){     
  64.         top[0]=temp;    
  65.         int i=0;    
  66.         for(;i<99&&temp>top[i+1];i++){    
  67.             top[i]=top[i+1];    
  68.         }    
  69.         top[i]=temp;    
  70.     }    
  71.  }    
  72.  @Override    
  73.  protected void cleanup(Context context) throws IOException,  InterruptedException {    
  74.     for(int i=0;i<100;i++){    
  75.         context.write(new IntWritable(top[i]), new IntWritable(top[i]));    
  76.     }    
  77.  }    
  78.   
  79.  public int run(String[] args) throws Exception {    
  80.     Configuration conf = getConf();    
  81.     Job job = new Job(conf, "TopKNum");    
  82.     job.setJarByClass(TopKNum.class);    
  83.     FileInputFormat.setInputPaths(job, new Path(args[0]));    
  84.     FileOutputFormat.setOutputPath(job, new Path(args[1]));    
  85.     job.setMapperClass(MapClass.class);    
  86.     job.setCombinerClass(Reduce.class);    
  87.     job.setReducerClass(Reduce.class);    
  88.     job.setInputFormatClass(TextInputFormat.class);    
  89.     job.setOutputFormatClass(TextOutputFormat.class);    
  90.     job.setOutputKeyClass(IntWritable.class);    
  91.     job.setOutputValueClass(IntWritable.class);    
  92.     System.exit(job.waitForCompletion(true) ? 0 : 1);    
  93.     return 0;    
  94.  }    
  95.  public static void main(String[] args) throws Exception {    
  96.     int res = ToolRunner.run(new Configuration(), new TopKNum(), args);    
  97.     System.exit(res);    
  98.     }    
  99. }    
  100.   
  101.  /*  
  102.  * 列举一部分出来:  
  103.  * 306 306  
  104.  307 307  
  105.  309 309  
  106.  313 313  
  107.  320 320  
  108.  346 346  
  109.  348 348  
  110.  393 393  
  111.  394 394  
  112.  472 472  
  113.  642 642  
  114.  706 706  
  115.  868 868  
  116.  */    

至此,处理海量数据我们有了新的思路:MapReduce + hadoop

再次感慨,mapReduce真是海量数据处理的神器~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值