您的位置:首页 > 运维架构

数据排序之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个数据大的所有数字在数组的右边。

/**
* 最小的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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  TopK