• TopK问题


    TopK问题是指从大量数据(源数据)中获取最大(或最小)的K个数据。

    TopK问题是个很常见的问题:例如学校要从全校学生中找到成绩最高的500名学生,再例如某搜索引擎要统计每天的100条搜索次数最多的关键词。

    对于这个问题,解决方法有很多:

    方法一:对源数据中所有数据进行排序,取出前K个数据,就是TopK。

    但是当数据量很大时,只需要k个最大的数,整体排序很耗时,效率不高。

    方法二:维护一个K长度的数组a[],先读取源数据中的前K个放入数组,对该数组进行升序排序,再依次读取源数据第K个以后的数据,和数组中最小的元素(a[0])比较,如果小于a[0]直接pass,大于的话,就丢弃最小的元素a[0],利用二分法找到其位置,然后该位置前的数组元素整体向前移位,直到源数据读取结束。

    这比方法一效率会有很大的提高,但是当K的值较大的时候,长度为K的数据整体移位,也是非常耗时的。

    方法三

    对于这种问题,效率比较高的解决方法是使用最小堆

    最小堆(小根堆)是一种数据结构,它首先是一颗完全二叉树,并且,它所有父节点的值小于或等于两个子节点的值

    最小堆的存储结构(物理结构)实际上是一个数组。如下图:

    堆有几个重要操作:

    BuildHeap:将普通数组转换成堆,转换完成后,数组就符合堆的特性:所有父节点的值小于或等于两个子节点的值。

    Heapify(int i):当元素i的左右子树都是小根堆时,通过Heapify让i元素下降到适当的位置,以符合堆的性质。

    回到上面的取TopK问题上,用最小堆的解决方法就是:先去源数据中的K个元素放到一个长度为K的数组中去,再把数组转换成最小堆。再依次取源数据中的K个之后的数据和堆的根节点(数组的第一个元素)比较,根据最小堆的性质,根节点一定是堆中最小的元素,如果小于它,则直接pass,大于的话,就替换掉跟元素,并对根元素进行Heapify,直到源数据遍历结束。

    最小堆的实现:

    [java] view plain copy
     
    1. public class MinHeap  
    2. {  
    3.     // 堆的存储结构 - 数组  
    4.     private int[] data;  
    5.       
    6.     // 将一个数组传入构造方法,并转换成一个小根堆  
    7.     public MinHeap(int[] data)  
    8.     {  
    9.         this.data = data;  
    10.         buildHeap();  
    11.     }  
    12.       
    13.     // 将数组转换成最小堆  
    14.     private void buildHeap()  
    15.     {  
    16.         // 完全二叉树只有数组下标小于或等于 (data.length) / 2 - 1 的元素有孩子结点,遍历这些结点。  
    17.         // *比如上面的图中,数组有10个元素, (data.length) / 2 - 1的值为4,a[4]有孩子结点,但a[5]没有*  
    18.         for (int i = (data.length) / 2 - 1; i >= 0; i--)   
    19.         {  
    20.             // 对有孩子结点的元素heapify  
    21.             heapify(i);  
    22.         }  
    23.     }  
    24.       
    25.     private void heapify(int i)  
    26.     {  
    27.         // 获取左右结点的数组下标  
    28.         int l = left(i);    
    29.         int r = right(i);  
    30.           
    31.         // 这是一个临时变量,表示 跟结点、左结点、右结点中最小的值的结点的下标  
    32.         int smallest = i;  
    33.           
    34.         // 存在左结点,且左结点的值小于根结点的值  
    35.         if (l < data.length && data[l] < data[i])    
    36.             smallest = l;    
    37.           
    38.         // 存在右结点,且右结点的值小于以上比较的较小值  
    39.         if (r < data.length && data[r] < data[smallest])    
    40.             smallest = r;    
    41.           
    42.         // 左右结点的值都大于根节点,直接return,不做任何操作  
    43.         if (i == smallest)    
    44.             return;    
    45.           
    46.         // 交换根节点和左右结点中最小的那个值,把根节点的值替换下去  
    47.         swap(i, smallest);  
    48.           
    49.         // 由于替换后左右子树会被影响,所以要对受影响的子树再进行heapify  
    50.         heapify(smallest);  
    51.     }  
    52.       
    53.     // 获取右结点的数组下标  
    54.     private int right(int i)  
    55.     {    
    56.         return (i + 1) << 1;    
    57.     }     
    58.   
    59.     // 获取左结点的数组下标  
    60.     private int left(int i)   
    61.     {    
    62.         return ((i + 1) << 1) - 1;    
    63.     }  
    64.       
    65.     // 交换元素位置  
    66.     private void swap(int i, int j)   
    67.     {    
    68.         int tmp = data[i];    
    69.         data[i] = data[j];    
    70.         data[j] = tmp;    
    71.     }  
    72.       
    73.     // 获取对中的最小的元素,根元素  
    74.     public int getRoot()  
    75.     {  
    76.             return data[0];  
    77.     }  
    78.   
    79.     // 替换根元素,并重新heapify  
    80.     public void setRoot(int root)  
    81.     {  
    82.         data[0] = root;  
    83.         heapify(0);  
    84.     }  
    85. }  

    利用最小堆获取TopK:

    [java] view plain copy
     
    1. public class TopK  
    2. {  
    3.     public static void main(String[] args)  
    4.     {  
    5.         // 源数据  
    6.         int[] data = {56,275,12,6,45,478,41,1236,456,12,546,45};  
    7.           
    8. // 获取Top5  
    9.         int[] top5 = topK(data, 5);  
    10.           
    11.         for(int i=0;i<5;i++)  
    12.         {  
    13.             System.out.println(top5[i]);  
    14.         }  
    15.     }  
    16.       
    17.     // 从data数组中获取最大的k个数  
    18.     private static int[] topK(int[] data,int k)  
    19.     {  
    20.         // 先取K个元素放入一个数组topk中  
    21.         int[] topk = new int[k];   
    22.         for(int i = 0;i< k;i++)  
    23.         {  
    24.             topk[i] = data[i];  
    25.         }  
    26.           
    27.         // 转换成最小堆  
    28.         MinHeap heap = new MinHeap(topk);  
    29.           
    30.         // 从k开始,遍历data  
    31.         for(int i= k;i<data.length;i++)  
    32.         {  
    33.             int root = heap.getRoot();  
    34.               
    35.             // 当数据大于堆中最小的数(根节点)时,替换堆中的根节点,再转换成堆  
    36.             if(data[i] > root)  
    37.             {  
    38.                 heap.setRoot(data[i]);  
    39.             }  
    40.         }  
    41.           
    42.         return topk;  
    43. }  
    44. }  
  • 相关阅读:
    封装
    魔术方法类与有关面向对象的关键字
    JS基础
    轮播效果
    进度条效果
    2018年6月
    2018年5月
    Monte Carlo tree search 学习
    2018年4月
    pachi 学习
  • 原文地址:https://www.cnblogs.com/lnas01/p/5927343.html
Copyright © 2020-2023  润新知