基于堆排序实现的找出N个数据的前M大数据之Java实现

一个10G的关键词的log,找出词频最高的前K个词,设可用内存为2G左右

    分析:

    本题的难点主要有两处,一是如何在有限内存下对大文件进行词频统计;二是如何在有限内存的下找出词频的前K大个词

1)词频统计

    词频统计,我们很自然的会想到使用hash。但是直接hash内存是放不下的啊…怎么办?其实对于有限内存下的大文件处理,都可总结为归并的思想,不过要注意归并时的分段亦是有学问的。请比较归并a与归并b方法

  • 归并a:将数据分为5段,分别对每段在内存中hash统计并将统计结果写入文件,然后合并分段的hash。

    问题:表面看来不错的主意,实则有很大问题,稍微想一下,a方法存在一个主要的困难就是,hash合并的时候相当于同时在5个文件中判断是否有相同的词,要这样做会非常繁琐。

    怎么解决呢?我当时是受编程珠玑中第一题(排序一千万个32位整数)启发的,它在归并分段的时候,不是直接的简单分段,而是每段取一个范围比如第一段取0~249000之间的数,这样有什么好处?将来的各段数彼此间是没有交集(重复)的。所以我们的思想就是要让这10G的关键词分段后各小段没有交集,这样hash合并就没有问题了。请看归并b

  • 归并b:将数据分为5段,分段时对每个词hash,hash值在一个范围中的词语分到一个段中,然后对于每个分段统计的hash结果直接都写入一个文件即可。

    分析:使用hash分段,保证各小段没有重复的词。我当时想的方法是将词语的首字拼音打头一样的分在一段中,例如“五谷丰登”、“万箭齐发”分到一起,都是w打头的拼音,其实这仅仅只是hash的一种。  

   归并a中的思路的弊端是这样的:

    在分段的时候,只是单纯的把10亿数据一刀切的分割,并没有多考虑其他的问题,在一刀切的时候,就可能会有两端中有相同的词条,也就是说在第一段和第二段中都会有相同的词条,将5段统计的hash结果都存储到文件中,再对五个hash结果合并,这个时候就需要检查hash各个结果中对相同词条统计的结果,也就是说对这五个hash结果进行再查找比对,相当于对这五个hash结果合并。这也是很繁琐的问题。

   但是在归并b中的思想是这样的:
    在分段的时候就限定这个问题的出现,具体方法就是,在分段的时候稍做处理,每个段中都没有重复的hash值,每个段中是一定范围的hash值。这样对每一段分别hash计算出现的频率,将最终的结果存放到文件中。这样对最后的五个hash结果进行比对,在这5*k(对每个段中分别查找相应的前k个,五段总共为5*k个)个结果中查找出现次数最多的前k个词条。


(ps:请注意!!!上面的问题并没有结束,这是在考虑hash的时候找到了比较好的思路,但是对于最终真正解决问题并没有起到用处,将5个hash结果存放到文件中,还是没有找出前k大,这个时候需要用到堆的问题,在第一个结果中建立k的最小堆(首先找到第一个hash结果的前k大,也就是第一个hash结果中出现频率最高的k个词条,然后从剩下的hash结果中更新这个堆),然后在剩下的堆中查找,如果有出现更频繁的词条,说明需要替换堆中元素,在整个过程中,需要N+N'logk的时间复杂度,hash查询需要N,调整堆需要logK,可能需要调整N'次,这里的N'就是没有所有的词条的种类)


算法思想:

当有N个数据,而

N又是非常大(比如:千万或者亿),需要找出N条数据的排名最前的M条数据时,可以采用的一种策略。

先选前M个元素组成一个小根堆,然后遍历剩下的数据,如果第i个元素key大于小根堆的根结点,就删除这个根结点,并将元素key插入根结点,调整这个堆使其成为小根堆,然后继续遍历剩下的数据; 最后,小根堆中的元素就是最大的M个元素。


代码实现如下:

[java]  view plain  copy
  1. "font-family:SimHei;">"font-family:SimHei;font-size:14px;">"font-size:14px;">HeapSort Class  

[java]  view plain  copy
  1. public abstract class HeapSort {  
  2.     public abstract boolean compare(E value1, E value2);//value1小于value2则返回true  
  3.       
  4.     public boolean heapSort(List list){//排序  
  5.         return heapSort(list, list.size());  
  6.     }  
  7.       
  8.     public boolean heapSort(List list, int n){  
  9.         if(null == list || 0 == list.size()){  
  10.             return false;  
  11.         }  
  12.         if(!heapCreate(list, n)){  
  13.             return false;  
  14.         }  
  15.         for(int i = n; i > 0; --i){  
  16.             swap(list, 0, i - 1);  
  17.             heapAdjust(list, 0, i - 1);  
  18.         }  
  19.         return true;  
  20.     }  
  21.       
  22.     public boolean heapCreate(List list, int length){ //创建小根堆  
  23.         if(null == list || 0 == list.size()){  
  24.             return false;  
  25.         }  
  26.         for(int i = (length / 2 - 1); i >= 0; --i){  
  27.             if(!heapAdjust(list, i, length)){  
  28.                 return false;  
  29.             }  
  30.         }  
  31.         return true;  
  32.     }  
  33.       
  34.     public boolean heapAdjust(List list, int middle, int length){//调整堆,使其满足小根堆的条件  
  35.         if(null == list || 0 == list.size()){  
  36.             return false;  
  37.         }  
  38.         E temp = list.get(middle);  
  39.         for(int i = (2 * middle + 1); i < length; i *= 2){  
  40.             if(i < (length - 1) && !this.compare(list.get(i), list.get(i + 1))){  
  41.                 ++i;  
  42.             }  
  43.             if(this.compare(temp,list.get(i))){  
  44.                 break;  
  45.             }  
  46.             list.set(middle, list.get(i));  
  47.             middle = i;  
  48.         }  
  49.         list.set(middle, temp);  
  50.         return true;  
  51.     }  
  52.       
  53.     public void swap(List list, int i, int j){//数据交换  
  54.         E temp = list.get(i);  
  55.         list.set(i, list.get(j));  
  56.         list.set(j, temp);  
  57.     }  
  58. }  

FindFirstNData Class

[java]  view plain  copy
  1. public abstract class FindFirstNData extends HeapSort{  
  2.   
  3.     public abstract boolean compare(E value1, E value2);  
  4.       
  5.     public boolean findFirstNData(List list, int n){  
  6.         if(!this.heapCreate(list, n)){  
  7.             return false;  
  8.         }  
  9.         for(int i = n; i < list.size(); ++i){  
  10.             if(!this.compare(list.get(0), list.get(i))){  
  11.                 continue;  
  12.             }  
  13.             this.swap(list, 0, i);  
  14.             if(!this.heapAdjust(list, 0, n)){  
  15.                 return false;  
  16.             }  
  17.         }  
  18.         return this.heapSort(list, n);  
  19.     }  
  20. }  

测试数据:给出10000000个Integer型的随机数据,找出前3大所用的时间如下图所示(单位:毫秒):





转自:http://blog.csdn.net/yusiguyuan/article/details/12882309

http://blog.csdn.net/awen_c/article/details/34100887




你可能感兴趣的:(算法)