前言
堆的其他应用
TopK 问题
TopK 问题:给你100万个数据,让你找到前10个最大的元素.
思路一:对整体进行排序,输出前10个最大的元素
基于比较的排序最快的时间复杂度是N*log以2为底的N.
对整体进行排序肯定不是一个非常好的思路
(4条消息) 拜托,面试别再问我TopK了!!!_架构师之路_的博客-CSDN博客
另外一种算法的链接,大家可以看一下
思路二:用堆排序
这不是Top k的做法,是堆
思路三:
假设有这么一组数据,还是要找前三个最大的值
我们取这五个数据演示一下
问题:找到前3个最大的元素.
1.先把前3(也就是前K)个元素创建为小根堆
当前的堆为什么是小根堆,因为堆顶的元素,一定是当前k个元素当中最小的一个元素.
如果,有元素x比堆顶元素大,那么x这个元素,可能就是topk中的一个
相反,如果是大根堆,那就不一定了
如果堆顶元素小,那么出堆顶元素,然后入当前比堆顶大的元素,然后再次调整为小根堆
此时我们就找到了前k个最大的元素
此时堆的大小只有k这么大
Topk时间复杂度:首先遍历整个元素O(N)
调整这个堆,也就是树的高度:log以2为底的k
所以时间复杂度就是:O(n*logk),如果k的值非常小就是O(n)
总结:
1.如果求前k个最大的元素,要建一个小根堆
2.如果求前k个最小的元素,要建一个大根堆
3.第k大的元素,建一个小堆,堆顶元素就是第k大的元素
4.第k小的元素
堆排序
借助堆如何对整体元素进行排序
对这一组数据进行从小到大的排序
问题:借助大根堆还是小根堆排序?
大根堆
1.调整为大根堆
2.0下标和最后1个未排序的元素进行交换即可
3.end--
4.end为0之后也就有序了
此时一定能够保证65是最大的, 因为本身如果对整个数组进行排序之后,65也一定是在数组的最后一个元素,所以0下标元素和最后一个元素交换,交换完之后只有0下标这个棵树不是大根堆,其他树并没有动过,那么就调整0下标这棵树,调整的话肯定不能包含65,相当于把65删掉了
调整过后又变成了大根堆
下一次依次类推往后走
也就是说一开始有个end下标,保存的是待排序排序序列未排序的最后一个元素
此时65已经有序了,end--
此时再把49和此时end下标的值15进行交换
换完之后49就有序了
调整之后end--
然后让37和18进行交换
这时37就有序了
堆排序代码
public void heapSort(){ int end = this.usedSize - 1; while(end > 0){ int tmp = elem[0]; elem[0] = elem[end]; elem[end] = tmp; shiftDown(0,end); end--; } }
java对象的比较
优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够 进行比较
offer(null)之后就报空指针异常了
为什么要能够进行比较呢
因为PriorityQueue默认底下是一个小根堆
存了第一个元素,再存第二个元素就得跟第一个元素进行比较
第二个元素和第一个元素进行比较,假设都是两张牌
根据什么比较呢?并没有指定
放的是整数可以自己比较大小,但是放的是自定义的两张牌怎么比较,到底谁大谁小
关于对象的比较来说:
1.equals(); 比较两个对象相不相同,返回值是一个->true或者false
2.比较大小:comparable,comeparTo
我们需要
实现compareTo,同时传一个Card,来设置怎么比较
比如根据数字比较:
此时我们在打印一下priorityQueue
打印结果:
这个时候就不会报错了
这种方式有个不好的地方,
一旦写好了根据哪种规则比较,那么就不能轻易进行修改了
另一种方式:
我们不给这个类实现接口,我们来写一些比较器
此时来比较
打印结果 :
既然可以用比较器,那么我们的优先级队列是不是也可以改一改
这样我们的运行结果还是报错
此时我们给队列传一个比较器
此时打印结果
另一种写法 :
匿名内部类
在一种写法:
可读性非常差
用代码实现TopK问题
- public class TopK {
- /**
- * 求数组当中前K个最小的元素
- * @param array
- * @param k
- * @return
- */
- public static int[] topK(int[] array,int k){
- //1.创建一个大小为k的大根堆
- PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
- @Override
- public int compare(Integer o1, Integer o2) {
- return o2 - o1;
- }
- });
- //2.遍历数组当中的元素,前K个元素放到队列当中
- for (int i = 0; i < array.length ; i++) {
- if(maxHeap.size() < k){
- maxHeap.offer(array[i]);
- }else {
- //3.从第k+1个元素开始,每个元素和堆顶元素进行比较
- int top = maxHeap.peek();
- if(top > array[i]){
- //3.1先弹出
- maxHeap.poll();
- //3.2后存入
- maxHeap.offer(array[i]);
- }
-
- }
- }
- int[] tmp = new int[k];
- for (int i = 0; i < k; i++) {
- tmp[i] = maxHeap.poll();
- }
- return tmp;
- }
-
- public static void main(String[] args) {
- int[] array = {18,21,8,10,34,12};
-
- }
- }
373. 查找和最小的 K 对数字 - 力扣(LeetCode)
- public static List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
- PriorityQueue <List<Integer>> maxHeap = new PriorityQueue<>(new Comparator<List<Integer>>() {
- @Override
- public int compare(List<Integer> o1, List<Integer> o2) {
- return (o2.get(0)+ o2.get(1)) - (o1.get(0) + o1.get(1));
- }
- });
- for (int i = 0; i < Math.min(nums1.length,k); i++) {
- for (int j = 0; j < Math.min(nums2.length,k); j++) {
- if(maxHeap.size() < k){
- List<Integer> tmpList = new ArrayList<>();
- tmpList.add(nums1[i]);
- tmpList.add(nums2[j]);
- maxHeap.offer(tmpList);
- }else{
- int top = maxHeap.peek().get(0) + maxHeap.peek().get(1);
- if(top >nums1[i] + nums2[j] ){
- maxHeap.poll();
- List<Integer> tmpList = new ArrayList<>();
- tmpList.add(nums1[i]);
- tmpList.add(nums2[j]);
- maxHeap.offer(tmpList);
-
- }
- }
- }
- }
- List<List<Integer>> ret = new ArrayList<>();
- for (int i = 0; i < k && !maxHeap.isEmpty(); i++) {
- ret.add(maxHeap.poll());
- }
- return ret;
-
- }
-
- public static void main(String[] args) {
- int[] num1 = {1,7,11};
- int[] num2 = {2,4,6};
-
- List<List<Integer>> ret = kSmallestPairs(num1,num2,3);
- System.out.println(ret);
-
- }