基于堆排序实现的找出N个数据的前M大数据之Java实现
2017-02-12 12:22
239 查看
一个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又是非常大(比如:千万或者亿),需
4000
要找出N条数据的排名最前的M条数据时,可以采用的一种策略。
先选前M个元素组成一个小根堆,然后遍历剩下的数据,如果第i个元素key大于小根堆的根结点,就删除这个根结点,并将元素key插入根结点,调整这个堆使其成为小根堆,然后继续遍历剩下的数据; 最后,小根堆中的元素就是最大的M个元素。
代码实现如下:
[java] view
plain copy
<span style="font-family:SimHei;"><span style="font-family:SimHei;font-size:14px;"><span style="font-size:14px;">HeapSort Class</span></span></span>
[java] view
plain copy
public abstract class HeapSort<E> {
public abstract boolean compare(E value1, E value2);//value1小于value2则返回true
public boolean heapSort(List<E> list){//排序
return heapSort(list, list.size());
}
public boolean heapSort(List<E> list, int n){
if(null == list || 0 == list.size()){
return false;
}
if(!heapCreate(list, n)){
return false;
}
for(int i = n; i > 0; --i){
swap(list, 0, i - 1);
heapAdjust(list, 0, i - 1);
}
return true;
}
public boolean heapCreate(List<E> list, int length){ //创建小根堆
if(null == list || 0 == list.size()){
return false;
}
for(int i = (length / 2 - 1); i >= 0; --i){
if(!heapAdjust(list, i, length)){
return false;
}
}
return true;
}
public boolean heapAdjust(List<E> list, int middle, int length){//调整堆,使其满足小根堆的条件
if(null == list || 0 == list.size()){
return false;
}
E temp = list.get(middle);
for(int i = (2 * middle + 1); i < length; i *= 2){
if(i < (length - 1) && !this.compare(list.get(i), list.get(i + 1))){
++i;
}
if(this.compare(temp,list.get(i))){
break;
}
list.set(middle, list.get(i));
middle = i;
}
list.set(middle, temp);
return true;
}
public void swap(List<E> list, int i, int j){//数据交换
E temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
}
FindFirstNData Class
[java] view
plain copy
public abstract class FindFirstNData<E> extends HeapSort<E>{
public abstract boolean compare(E value1, E value2);
public boolean findFirstNData(List<E> list, int n){
if(!this.heapCreate(list, n)){
return false;
}
for(int i = n; i < list.size(); ++i){
if(!this.compare(list.get(0), list.get(i))){
continue;
}
this.swap(list, 0, i);
if(!this.heapAdjust(list, 0, n)){
return false;
}
}
return this.heapSort(list, n);
}
}
测试数据:给出10000000个Integer型的随机数据,找出前3大所用的时间如下图所示(单位:毫秒):
转自:http://blog.csdn.net/yusiguyuan/article/details/12882309
http://blog.csdn.net/awen_c/article/details/34100887
分析:
本题的难点主要有两处,一是如何在有限内存下对大文件进行词频统计;二是如何在有限内存的下找出词频的前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又是非常大(比如:千万或者亿),需
4000
要找出N条数据的排名最前的M条数据时,可以采用的一种策略。
先选前M个元素组成一个小根堆,然后遍历剩下的数据,如果第i个元素key大于小根堆的根结点,就删除这个根结点,并将元素key插入根结点,调整这个堆使其成为小根堆,然后继续遍历剩下的数据; 最后,小根堆中的元素就是最大的M个元素。
代码实现如下:
[java] view
plain copy
<span style="font-family:SimHei;"><span style="font-family:SimHei;font-size:14px;"><span style="font-size:14px;">HeapSort Class</span></span></span>
[java] view
plain copy
public abstract class HeapSort<E> {
public abstract boolean compare(E value1, E value2);//value1小于value2则返回true
public boolean heapSort(List<E> list){//排序
return heapSort(list, list.size());
}
public boolean heapSort(List<E> list, int n){
if(null == list || 0 == list.size()){
return false;
}
if(!heapCreate(list, n)){
return false;
}
for(int i = n; i > 0; --i){
swap(list, 0, i - 1);
heapAdjust(list, 0, i - 1);
}
return true;
}
public boolean heapCreate(List<E> list, int length){ //创建小根堆
if(null == list || 0 == list.size()){
return false;
}
for(int i = (length / 2 - 1); i >= 0; --i){
if(!heapAdjust(list, i, length)){
return false;
}
}
return true;
}
public boolean heapAdjust(List<E> list, int middle, int length){//调整堆,使其满足小根堆的条件
if(null == list || 0 == list.size()){
return false;
}
E temp = list.get(middle);
for(int i = (2 * middle + 1); i < length; i *= 2){
if(i < (length - 1) && !this.compare(list.get(i), list.get(i + 1))){
++i;
}
if(this.compare(temp,list.get(i))){
break;
}
list.set(middle, list.get(i));
middle = i;
}
list.set(middle, temp);
return true;
}
public void swap(List<E> list, int i, int j){//数据交换
E temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
}
FindFirstNData Class
[java] view
plain copy
public abstract class FindFirstNData<E> extends HeapSort<E>{
public abstract boolean compare(E value1, E value2);
public boolean findFirstNData(List<E> list, int n){
if(!this.heapCreate(list, n)){
return false;
}
for(int i = n; i < list.size(); ++i){
if(!this.compare(list.get(0), list.get(i))){
continue;
}
this.swap(list, 0, i);
if(!this.heapAdjust(list, 0, n)){
return false;
}
}
return this.heapSort(list, n);
}
}
测试数据:给出10000000个Integer型的随机数据,找出前3大所用的时间如下图所示(单位:毫秒):
转自:http://blog.csdn.net/yusiguyuan/article/details/12882309
http://blog.csdn.net/awen_c/article/details/34100887
相关文章推荐
- 基于堆排序实现的找出N个数据的前M大数据之Java实现
- 数据挖掘-基于贝叶斯算法及KNN算法的newsgroup18828文本分类器的JAVA实现(上)
- 基于java数据采集串口通讯的设计和实现
- 数据挖掘-基于贝叶斯算法及KNN算法的newsgroup18828文本分类器的JAVA实现(上)
- Java实现升序排列的整形数组A,元素两两不相等找出A[i]=i的数据
- 数据结构之基于Java的顺序列表实现
- 数据脱敏——基于Java自定义注解实现日志字段脱敏
- 数据挖掘-基于贝叶斯算法及KNN算法的newsgroup18828文档分类器的JAVA实现(下)
- 数据挖掘-基于贝叶斯算法及KNN算法的newsgroup18828文本分类器的JAVA实现(上)
- 用java实现一个单线程基于控制台和以文件为数据存储并加上一点MVC思想的ATM
- java实现基于SM4算法计算联机MAC数据
- 数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(下)
- 使用 Google App Engine 实现基于云计算的小型 Java 数据服务应用
- 数据挖掘-基于贝叶斯算法及KNN算法的newsgroup18828文本分类器的JAVA实现(上)
- 数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(下)
- 数据结构之基于Java的二叉树实现
- 数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(上)
- 数据结构书中基于整数的简单排序Java实现,巩固一下基础
- 数据结构之基于Java的链接栈实现
- Zabbix实践(五):基于java的zabbix api调用实现数据共享