数据结构和算法 之 两种方式实现TOP K

​    目录

                          1、背景介绍

                          2、Redis实现

                          3、PriorityQueue实现


一、背景介绍

什么是 Top K 问题?简单来说就是在一堆数据里面找到前 K 大(也可以是前 K 小)的数。这个问题也是十分经典的算法问题,不论是面试中还是实际开发中,都非常具有代表性。

既然是要查询前 K 大的数,那么最容易想到的方法就是进行排序了,通过如快排(O(nlogn))等效率较高的排序算法对给定的序列进行排序,然后取k个最大数或者最小数。这种方式固然实现简单,但是我们只需要k(k<n)个数,排序却要对全部n个数进行操作,固然会增加时间复杂度,所以这里必然存在着优化的空间。

二、Redis实现

通过Redis提供的Sorted Sets可以非常容易地实现TOP K问题,Redis是通过Skip List(跳跃表)数据结构实现的,熟悉Skip List(跳跃表)的同学应该会知道Skip List(跳跃表)添加、修改元素的时间复杂度为O(log(N)),是一种高效的数据结构,当我们要求排序的时候,Redis根本不需要做任何工作了,早已经全部排好序了。Java中ConcurrentSkipListSet、ConcurrentSkipListMap均是由Skip List(跳跃表)实现的。

三、PriorityQueue实现

PriorityQueue也叫优先队列,是一种二叉树数据结构。其特点是不管左右相邻结点之间的关系,但是总是保证上面的结点元素大于下层结点(大顶堆)或者上面的结点元素小于下层结点(小顶堆)。

PriorityQueue类在Java1.5中引入。是一种基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。要求使用Java Comparable和Comparator接口给对象排序,并且在排序时会按照优先级处理其中的元素。

其在Java中的类结构如下:

对于TOP K问题,可以使用最小堆进行实现,实现思路如下:

建立一个容量为k的小堆顶,并从数组中逐个取数,如果堆的容量小于k,则向堆中添加元素;如果堆的容量等于k,则比较取出的数和堆顶元素。堆顶元素始终是堆中k个元素中的最小值,如果取出的数比堆顶元素大,则删除堆顶元素而把该数加入堆中。直至数组中所有元素都访问一遍,当遍历完一次数组后,堆中的k个数即为数组中最大的k个数。

Java代码实现如下:

/**
 * @author: wenyixicodedog
 * @create: 2020-07-25
 * @description:
 */
public class TOPK {
​
    public static int[] findTOPK(int[] a, int k) {
​
        PriorityQueue<Integer> queue = new PriorityQueue<>(k);
​
        Arrays.stream(a).forEach((item) -> {
            if (queue.size() < k) {
                queue.offer(item);
            } else if (queue.peek() < item) {
                queue.poll();
                queue.offer(item);
            }
        });
​
        int[] result = new int[k];
        for (int i = 0; !queue.isEmpty(); i++) {
            result[i] = queue.poll();
        }
        return result;
    }
​
    public static void main(String[] args) {
        int[] a = new int[]{11, 61, 21, 33, 54, 4, 82, 77, 98};
        System.out.println(Arrays.toString(findTOPK(a, 3)));
    }
​
}

PriorityQueue凭借其本身独特的优势,在很多场景中都发挥着作用,例如处理海量数据中的TOP K 、海量数据中找出出现频率最高的数、堆排序等。

PriorityQueue源码实现中是一种没有加锁的实现方式,所以是非线程安全的,如果要使用线程安全的优先队列,可以使用PriorityBlockingQueue。

 

更多内容持续更新中,感兴趣的朋友请移步至个人公众号,谢谢支持😜😜......

公众号:wenyixicodedog

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值