目录
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