开发面试 TopK问题 从排序算法优化的角度分析海量数据TopK优化问题

海量数据的TopK问题几乎是后台开发面试必备题,本文从排序算法从0优化的角度分析TopK问题的优化。
什么是TopK问题:给定一个很大的数据量n,要求从n中提取出最大/最小/重复频度最高的K个数(K相对于n较小,如n为10亿量级,而K为100)。

解决这个问题,很容易想到要使用排序算法,首先,使用方法1笨办法 – 全部排序,解出来再说。

  1. 将n个数全部排序

    使用普通排序,将n个数全部排序之后,取出最大的k个,即为所得。

    时间复杂度:O(n*lg(n))

    分析 & 优化思路明明只需要TopK,却将全局都排序了,这也是这个方法复杂度非常高的原因。那能不能不全局排序,而只局部排序呢?这就引出了第二个优化方法。

  2. 只将TopK个数排序

    使用冒泡排序

    时间复杂度:O(n*k)

    分析:冒泡,将全局排序优化为了局部排序,非TopK的元素是不需要排序的,节省了计算资源。不少朋友会想到,需求是TopK,是不是这最大的k个元素也不需要排序呢?这就引出了第三个优化方法。

  3. 把TopK和非TopK分为两类,均不排序

    使用堆排序,只找到TopK,不排序TopK

    时间复杂度:O(n*lg(k))

    分析:堆,将冒泡的TopK排序优化为了TopK不排序,节省了计算资源。堆,是求TopK的经典算法

    最小的K个用最大堆,最大的K个用最小堆。

    JDK中PriorityQueue实现了数据结构堆,通过指定comparator字段来表示小顶堆或大顶堆,默认为null,表示自然序(natural ordering)。

    public int findKthLargest(int[] nums, int k) {
      PriorityQueue<Integer> minQueue = new PriorityQueue<>(k);
      for (int num : nums) {
        if (minQueue.size() < k || num > minQueue.peek())
          minQueue.offer(num);
        if (minQueue.size() > k)
          minQueue.poll();
      }
      return minQueue.peek();
    }
    
  4. 快排 - 随机选择算法 Quick - Select

    对只分两类不排序的进一步优化 – 堆排序需要遍历,而partition只需要遍历其中一个分支。

    TopK是希望求出arr[1,n]中最大的k个数,那如果找到了第k大的数,做一次partition,不就一次性找到最大的k个数了么?

    画外音:即partition后左半区的k个数。

    问题变成了arr[1, n]中找到第k大的数。

    再回过头来看看第一次partition,划分之后:

    i = partition(arr, 1, n); 
    
    • 如果i大于k,则说明arr[i]左边的元素都大于k,于是只递归arr[1, i-1]里第k大的元素即可;
    • 如果i小于k,则说明说明第k大的元素在arr[i]的右边,于是只递归arr[i+1, n]里第k-i大的元素即可;

    这就是随机选择算法randomized_select,RS,其伪代码如下:

    int RS(arr, low, high, k){ 
      if(low== high) return arr[low]; 
      i= partition(arr, low, high); 
      temp= i-low; //数组前半部分元素个数 
      if(temp>=k) 
          return RS(arr, low, i-1, k); //求前半部分第k大 
      else 
          return RS(arr, i+1, high, k-i); //求后半部分第k-i大 
    } 
    

    Quick - Select的Java实现

    public int findKthLargest(int[] nums, int k) {
      return quickSelect(nums, k, 0, nums.length - 1);
    }
    
    // quick select to find the kth-largest element
    public int quickSelect(int[] arr, int k, int left, int right) {
      if (left == right) return arr[right];
      int index = partition(arr, left, right);
      if (index - left + 1 > k)
        return quickSelect(arr, k, left, index - 1);
      else if (index - left + 1 == k)
        return arr[index];
      else
        return quickSelect(arr, k - index + left - 1, index + 1, right);
    
    }
    

猜你喜欢

转载自blog.csdn.net/whichard/article/details/88024510
今日推荐