能带你起飞的【数据结构】成王第十二篇:堆2

前言

 

堆的其他应用

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);

    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

K稳重

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值