您的位置:首页 > 大数据

基于堆排序实现的找出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




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐