Top N问题(一)基础

来源:互联网 发布:淘宝客服人工投诉电话 编辑:程序博客网 时间:2024/05/16 12:03

前言:

在分析MapReduce、Hive、Redis和Storm、Spark等工具实现分组Top n问题前,我们先看下Java最原始实现Top的方法有哪些,为后面奠定些基础,这也是我要整理成一个系列的原因。

对于Top n问题,这里根据数据特点用合并法【数组相对有序】、快排过程法【无序单个数组】、大小顶堆【最常用】和PriorityQueue固定队列四种方式来实现。

合并法:

数据描述:这种方法适用于几个数组有序的情况,来求Top k。

实现描述:采用Merge的方法,设定一个数组下标扫描位置记录临时数组和top结果数组,然后从临时数组记录下标开始遍历所有数组并比较大小,将最大值存入结果数组,最大值对应所在数组下标加一存入临时数组,以使其从下位开始遍历,时间复杂度为O(k*m)。(m:为数组的个数)。

具体实现:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package fjdm;  
  2. import java.util.ArrayList;  
  3. import java.util.List;  
  4. /** 
  5.  * 已知几个递减有序的m个数组,求这几个数据前k大的数 
  6.  * a[4,3,2,1],b[6,5,3,1] -> result[6,5,4] 
  7.  * @author 张恩备 
  8.  * @date 2016-11-25 上午10:57:03 
  9.  */  
  10. public class TopKByMerge{  
  11.  public static int[] getTopK(List<List<Integer>>input,int k){  
  12.     int index[]=new int[input.size()];//保存每个数组下标扫描的位置;  
  13.     int result[]=new int[k];  
  14.     for(int i=0;i<k;i++){  
  15.        int max=Integer.MIN_VALUE;  
  16.        int maxIndex=0;  
  17.        for(int j=0;j<input.size();j++){  
  18.            if(index[j]<input.get(j).size()){  
  19.                 if(max<input.get(j).get(index[j])){  
  20.                     max=input.get(j).get(index[j]);  
  21.                     maxIndex=j;  
  22.                 }  
  23.            }  
  24.        }  
  25.        if(max==Integer.MIN_VALUE){  
  26.            return result;  
  27.        }  
  28.        result[i]=max;  
  29.        index[maxIndex]+=1;  
  30.          
  31.     }  
  32.     return result;  
  33.  }   
  34.  public static void main(String[] args) {  
  35.      List<Integer> a = new ArrayList<Integer>();  
  36.      a.add(4);  
  37.      a.add(3);  
  38.      a.add(2);  
  39.      a.add(1);  
  40.      List<Integer> b = new ArrayList<Integer>();  
  41.      b.add(6);  
  42.      b.add(5);  
  43.      b.add(3);  
  44.      b.add(1);  
  45.      List<List<Integer>> ab = new ArrayList<List<Integer>>();  
  46.      ab.add(a);  
  47.      ab.add(b);  
  48.      int r[] = getTopK(ab, 3);  
  49.      for (int i = 0; i < r.length; i++) {  
  50.         System.out.println(r[i]);  
  51.     }  
  52. }  
  53. }  

快排过程法:

数据描述:适用于无序单个数组,快排过程法利用快速排序的过程来求Top k。

实现步骤:根据快排规则,选择一个数为基准(代码是以最后一个数)分化数据,并记录基准数最后的落点下标,最后判断下标和k-1值大小(下标从0开始),不相等就继续朝k-1数量方向分化:

下标小于k-1,对下标右侧(partion,end)继续二分;

下标大于k-1,对下标左侧(first,partion)继续二分;

直到k个数为top,但这k个数并没有顺序。

具体实现:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package fjdm;  
  2.   
  3. /** 
  4.  * 利用快速排序的过程来求最小的k个数 
  5.  * @author 张恩备 
  6.  * @date 2016-11-25 上午11:59:45 
  7.  */  
  8. public class TopK{  
  9.     int partion(int a[],int first,int end){  
  10.           int i=first;  
  11.           int main=a[end];  
  12.           for(int j=first;j<end;j++){  
  13.                  if(a[j]<main){  
  14.                      int temp=a[j];  
  15.                      a[j]=a[i];  
  16.                      a[i]=temp;  
  17.                      i++;  
  18.                  }  
  19.           }  
  20.           a[end]=a[i];  
  21.           a[i]=main;  
  22.           return i;    
  23.     }  
  24.     void getTopKMinBySort(int a[],int first,int end,int k){  
  25.         if(first<end){  
  26.              int partionIndex=partion(a,first,end);  
  27.              if(partionIndex==k-1)return;  
  28.              else if(partionIndex>k-1)getTopKMinBySort(a,first,partionIndex-1,k);  
  29.              else getTopKMinBySort(a,partionIndex+1,end,k);  
  30.         }  
  31.     }  
  32. public static void main(String []args){  
  33.         int a[]={2,20,3,7,9,1,17,18,0,4};  
  34.         int k=6;  
  35.         new TopK().getTopKMinBySort(a,0,a.length-1,k);  
  36.         for(int i=0;i<k;i++){  
  37.             System.out.print(a[i]+" ");  
  38.         }  
  39.     }  
  40. }  

采用小顶堆或者大顶堆:

数据描述:求最大K个采用小顶堆,而求最小K个采用大顶堆。

实现步骤:根据数据前K个建立K个节点的小顶堆,在后面的N-K的数据的扫描中,

如果数据大于小顶堆的根节点,则根节点的值覆为该数据,并调节节点至小顶堆。

如果数据小于或等于小顶堆的根节点,小根堆无变化。

求最小K个跟这求最大K个类似。时间复杂度O(nlogK)(n:数据的长度),特别适用于大数据的求Top K。

具体实现:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package fjdm;  
  2.   
  3. /** 
  4.  * 求前面的最大K个 解决方案:小根堆 (数据量比较大(特别是大到内存不可以容纳)时,偏向于采用堆) 
  5.  * @author 张恩备 
  6.  * @date 2016-11-25 下午12:15:36 
  7.  */  
  8. public class TopKByHeap {  
  9.     /** 
  10.      * 创建k个节点的小根堆 
  11.      *  
  12.      * @param a 
  13.      * @param k 
  14.      * @return 
  15.      */  
  16.     int[] createHeap(int a[], int k) {  
  17.         int[] result = new int[k];  
  18.         for (int i = 0; i < k; i++) {  
  19.             result[i] = a[i];  
  20.         }  
  21.         for (int i = 1; i < k; i++) {  
  22.             int child = i;  
  23.             int parent = (i - 1) / 2;  
  24.             int temp = a[i];  
  25.             while (parent >= 0 &&child!=0&& result[parent] >temp) {  
  26.                 result[child] = result[parent];  
  27.                 child = parent;  
  28.                 parent = (parent - 1) / 2;  
  29.             }  
  30.             result[child] = temp;  
  31.         }  
  32.         return result;  
  33.   
  34.     }  
  35.   
  36.     void insert(int a[], int value) {  
  37.          a[0]=value;  
  38.          int parent=0;  
  39.            
  40.          while(parent<a.length){  
  41.              int lchild=2*parent+1;  
  42.              int rchild=2*parent+2;  
  43.              int minIndex=parent;  
  44.              if(lchild<a.length&&a[parent]>a[lchild]){  
  45.                  minIndex=lchild;  
  46.              }  
  47.              if(rchild<a.length&&a[minIndex]>a[rchild]){  
  48.                  minIndex=rchild;  
  49.              }  
  50.              if(minIndex==parent){  
  51.                  break;  
  52.              }else{  
  53.                  int temp=a[parent];  
  54.                  a[parent]=a[minIndex];  
  55.                  a[minIndex]=temp;  
  56.                  parent=minIndex;  
  57.              }  
  58.          }  
  59.     }  
  60.   
  61.     int[] getTopKByHeap(int input[], int k) {  
  62.         int heap[] = this.createHeap(input, k);  
  63.         for(int i=k;i<input.length;i++){  
  64.             if(input[i]>heap[0]){  
  65.                 this.insert(heap, input[i]);  
  66.             }  
  67.         }  
  68.         return heap;  
  69.     }  
  70.   
  71.     public static void main(String[] args) {  
  72.         int a[] = { 43512,8,9,10};  
  73.         int result[] = new TopKByHeap().getTopKByHeap(a, 3);  
  74.         for (int temp : result) {  
  75.             System.out.println(temp);  
  76.         }  
  77.     }  
  78. }  

PriorityQueue优先队列:

数据描述:PriorityQueue是从JDK1.5开始提供的新的数据结构接口,它是一种基于优先级堆的极大优先级队列。优先级队列是不同于先进先出队列的另一种队列。每次从队列中取出的是具有最高优先权的元素。如果不提供Comparator的话,优先队列中元素默认按自然顺序排列,也就是数字默认是小的在队列头,字符串则按字典序排列(参阅 Comparable),也可以根据 Comparator 来指定,这取决于使用哪种构造方法。优先级队列不允许 null 元素。依靠自然排序的优先级队列还不允许插入不可比较的对象(这样做可能导致 ClassCastException)。

实现步骤:PriorityQueue构造固定容量的优先队列,模拟大顶堆,这种队列本身数组实现,无容量限制,可以指定队列长度和比较方式,然后将数据依次压入,当队列满时会poll出小值,最后需要注意的是,priorityQueue本身遍历是无序的,可以使用内置poll()方法,每次从队首取出元素。

具体实现:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package fjdm;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collections;  
  5. import java.util.Comparator;  
  6. import java.util.Iterator;  
  7. import java.util.List;  
  8. import java.util.PriorityQueue;  
  9. import java.util.Random;  
  10. /** 
  11.  * 固定容量的优先队列,模拟大顶堆,用于解决求topN小的问题 
  12.  * @author 张恩备 
  13.  * @date 2016-11-25 下午02:29:31 
  14.  */  
  15. public class FixSizedPriorityQueue<E extends Comparable> {  
  16.     private PriorityQueue<E> queue;  
  17.     private int maxSize; // 堆的最大容量  
  18.   
  19.     public FixSizedPriorityQueue(int maxSize) {  
  20.         if (maxSize <= 0)  
  21.             throw new IllegalArgumentException();  
  22.         this.maxSize = maxSize;  
  23.         this.queue = new PriorityQueue(maxSize, new Comparator<E>() {  
  24.             public int compare(E o1, E o2) {  
  25.                 // 生成最大堆使用o2-o1,生成最小堆使用o1-o2, 并修改 e.compareTo(peek) 比较规则  
  26.                 return (o2.compareTo(o1));  
  27.             }  
  28.         });  
  29.     }  
  30.   
  31.     public void add(E e) {  
  32.         if (queue.size() < maxSize) { // 未达到最大容量,直接添加  
  33.             queue.add(e);  
  34.         } else { // 队列已满  
  35.             E peek = queue.peek();  
  36.             if (e.compareTo(peek) < 0) { // 将新元素与当前堆顶元素比较,保留较小的元素  
  37.                 queue.poll();  
  38.                 queue.add(e);  
  39.             }  
  40.         }  
  41.     }  
  42.   
  43.     public List<E> sortedList() {  
  44.         List<E> list = new ArrayList<E>(queue);  
  45.         Collections.sort(list); // PriorityQueue本身的遍历是无序的,最终需要对队列中的元素进行排序  
  46.         return list;  
  47.     }  
  48.   
  49.     public static void main(String[] args) {  
  50.         final FixSizedPriorityQueue pq = new FixSizedPriorityQueue(10);  
  51.         Random random = new Random();  
  52.         int rNum = 0;  
  53.         System.out.println("100 个 0~999 之间的随机数:-----------------------------------");  
  54.         for (int i = 1; i <= 100; i++) {  
  55.             rNum = random.nextInt(1000);  
  56.             System.out.println(rNum);  
  57.             pq.add(rNum);  
  58.         }  
  59.         System.out.println("PriorityQueue 本身的遍历是无序的:-----------------------------------");  
  60.         Iterable<Integer> iter = new Iterable<Integer>() {  
  61.             public Iterator<Integer> iterator() {  
  62.                 return pq.queue.iterator();  
  63.             }  
  64.         };  
  65.         for (Integer item : iter) {  
  66.             System.out.print(item + ", ");  
  67.         }  
  68.         System.out.println();  
  69.         System.out.println("PriorityQueue 排序后的遍历:-----------------------------------");  
  70.         /* 
  71.          * for (Integer item : pq.sortedList()) { System.out.println(item); } 
  72.          */  
  73.         // 或者直接用内置的 poll() 方法,每次取队首元素(堆顶的最大值)  
  74.         while (!pq.queue.isEmpty()) {  
  75.             System.out.print(pq.queue.poll() + ", ");  
  76.         }  
  77.     }  
  78. }  


0 0