数据排序之TopK问题
2016-05-27 15:27
495 查看
【前言】在大规模数据处理中,常遇到的一类问题是,在海量数据中找出出现频率最高的前K个数,或者从海量数据中找出最大的前K个数,这类问题通常称为“topK”问题
【解决思路】
针对topK类问题,通常比较好的方案是【分治+trie树/hash+小顶堆】,即先将数据集按照hash算法分解成多个小数据集,然后使用trie树或者hash表统计每个小数据集中的query词频,之后用小顶堆求出每个数据集中出频率最高的前K个数,最后在所有top
K中求出最终的top K。
实际上,最优的解决方案应该是最符合实际设计需求的方案,在实际应用中,可能有足够大的内存,那么直接将数据扔到内存中一次性处理即可,也可能机器有多个核,这样可以采用多线程处理整个数据集。
【解决】在得到数据后,如何对获得的数据进行topK排序呢。下面简单介绍几种常见方法。
【1】基于Partition(分区)解决TopK min的问题:根据数组的第K个数字来调整,使得比第K个数小的都在数组的左边,比第K个数据大的所有数字在数组的右边。
【2】基于小顶堆来实现TopK max的问题:维护一个K个数据的小顶堆,遍历元素,若元素大于堆顶元素,则将堆顶元素移除,当前元素插入堆顶,并进行调整。
复杂度分析:先用Hash表统计每个Query出现的次数,O(N);然后第二步、采用堆数据结构找出Top 10,N*O(logK)。所以,我们最终的时间复杂度是:O(N) + *O(N1*logK)。(N为数据总数,N1为不重元素个数)。
【3】基于优先级队列来实现的TopK问题:实现Comparator接口,使得队列元素按照升序/降序排序,维护一个长度为K的数组,把队列的元素出队放到数组中即可。
【参考资料】
《十道海量数据处理面试题与十个方法大总结》:http://blog.csdn.net/v_JULY_v/archive/2011/03/26/6279498.aspx
【解决思路】
针对topK类问题,通常比较好的方案是【分治+trie树/hash+小顶堆】,即先将数据集按照hash算法分解成多个小数据集,然后使用trie树或者hash表统计每个小数据集中的query词频,之后用小顶堆求出每个数据集中出频率最高的前K个数,最后在所有top
K中求出最终的top K。
实际上,最优的解决方案应该是最符合实际设计需求的方案,在实际应用中,可能有足够大的内存,那么直接将数据扔到内存中一次性处理即可,也可能机器有多个核,这样可以采用多线程处理整个数据集。
【解决】在得到数据后,如何对获得的数据进行topK排序呢。下面简单介绍几种常见方法。
【1】基于Partition(分区)解决TopK min的问题:根据数组的第K个数字来调整,使得比第K个数小的都在数组的左边,比第K个数据大的所有数字在数组的右边。
/** * 最小的K个数,O(N)解法,需要修改原数组 * @param a * @param n * @param b * @param k */ public void TopKmin(int[] a,int n,int[] b,int k){ if(a==null||k>n||n<=0||k<=0) return; int left=0; int right=n-1; int index=Partition(a,left,right); while(index!=k-1){ if(index>k-1) index=Partition(a, left, index-1); else index=Partition(a, index+1, right); } for(int i=0;i<k;i++){ b[i]=a[i]; } } private static int Partition(int[] a, int left, int right) { // TODO Auto-generated method stub int pivot=a[left]; //int random=(new Random().nextInt(right)%(right-left+1))+left; [left,right] 随机数 //int pivot=a[random]; while(left<right){ while(left<right&&a[right]>=pivot) right--; if(left<right) a[left++]=a[right]; while(left<right&&a[left]<=pivot) left++; if(left<right) a[right--]=a[left]; } a[left]=pivot; return left; }
【2】基于小顶堆来实现TopK max的问题:维护一个K个数据的小顶堆,遍历元素,若元素大于堆顶元素,则将堆顶元素移除,当前元素插入堆顶,并进行调整。
复杂度分析:先用Hash表统计每个Query出现的次数,O(N);然后第二步、采用堆数据结构找出Top 10,N*O(logK)。所以,我们最终的时间复杂度是:O(N) + *O(N1*logK)。(N为数据总数,N1为不重元素个数)。
@Test public void testBuildMINHeap(){ int[] a={49,38,65,97,76,13,49,78,34,12,64}; int k=3; int[] topK=new int[k]; for(int i=0;i<k;i++){ topK[i]=a[i]; } buildMinHeap(topK, k); for(int i=k;i<a.length;i++){ int root=topK[0]; //当数据大于根结点的时候,替换根节点,并进行更新堆 if(a[i]>root){ topK[0]=a[i]; //buildMinHeap(topK, k); heapfiy(topK, 0, k); } } //buildMinHeap(a,a.length); System.out.println(Arrays.toString(topK)); } public void heapfiy(int[] a,int i,int len){ //int len=a.length; int left=2*i+1; int right=2*i+2; int smallest=i; while(true){ if(left<len&&a[left]<a[smallest]) smallest=left; if(right<len&&a[right]<a[smallest]) smallest=right; if(i!=smallest){ int temp=a[i]; a[i]=a[smallest]; a[smallest]=temp; } else break; i=smallest; left=2*i+1; right=2*i+2; } } public void buildMinHeap(int[] a,int len){ //int len=a.length; for(int i=len/2-1;i>=0;i--){ heapfiy(a, i,len); } }
【3】基于优先级队列来实现的TopK问题:实现Comparator接口,使得队列元素按照升序/降序排序,维护一个长度为K的数组,把队列的元素出队放到数组中即可。
/* 队尾 |1 |3 |2 |2 |3 |1 队头 降序 升序 */ public static int[] topKLargest(int[] input, int k) {//降序 PriorityQueue<Integer> minheap = new PriorityQueue<Integer>(k, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if(o1 < o2) { return 1; } else if(o1 > o2) { return -1; } else { return 0; } } }); for (int i : input) { minheap.add(i); } int[] out = new int[k]; for (int i = 0; i < out.length; i++) { out[i] = minheap.poll(); } return out; } public static int[] topKSmallest(int[] input, int k) {//升序 PriorityQueue<Integer> minheap = new PriorityQueue<Integer>(k, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if(o1 < o2) { return -1; } else if(o1 > o2) { return 1; } else { return 0; } } }); for (int i : input) { minheap.add(i); } int[] out = new int[k]; for (int i = 0; i < out.length; i++) { out[i] = minheap.poll(); } return out; } public static void main(String[] args) { int[] out = topKLargest(new int[] { 10, 23, 5, 1, 7, 8, 4, 5, 7, 12, 523 }, 3); System.out.print("Largest: "); for (int o : out) { System.out.print(o + " "); } out = topKSmallest(new int[] { 10, 23, 5, 1, 7, 8, 4, 5, 7, 12, 523 }, 3); System.out.println(); System.out.print("Largest: "); for (int o : out) { System.out.print(o + " "); } }
【参考资料】
《十道海量数据处理面试题与十个方法大总结》:http://blog.csdn.net/v_JULY_v/archive/2011/03/26/6279498.aspx
相关文章推荐
- Top K Frequent Elements
- 找出n个数中最大的m个数的一些解决办法
- java实现top K排序
- 关于海量数据TopK问题的一个具体实现(java)
- topk问题C++实现
- 哈希表详解
- 如何扩充数据结构
- Mapreduce求TopK最大值
- 纯C++11标准写类topk算法(不稳定排序)类模板
- 从两个有序数组的并集中寻找第k小元素
- MapReduce TopK 文件
- 怎样从10亿查询词找出出现频率最高的10个
- 面试题—— 找出一个无序整型数组中第k大的数。
- MapReduce实现TopK
- TopK算法及其实现
- Spark 编程实战之经典算法TOP K
- key-value的topK问题
- Spark 入门之十一:Spark数据处理常用的那几招
- MapReduce实现分组排序
- leetcode解题之4. Median of Two Sorted Arrays java (两个排序数组的中位数)