Java:PriorityQueue详解——从0基础到真应用

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java:PriorityQueue详解——从0基础到真应用相关的知识,希望对你有一定的参考价值。

文章目录

前言

看了网上的详解,我反是一篇没看懂,都是什么垃圾玩意也发出来,无语,我自己根据这两天的学习总结出来了下面这些干货,我就是这么学会的,大家要是零基础看肯定木问题

概念

官网是这么说的:一个基于优先级堆的无界优先级队列。
(害,就是个优先级队列就完了,静这车轱辘话)

优先级队列的元素进行排序,根据他们的自然排序,或通过设置在队列 Comparator 排序规则,这取决于使用哪个构造函数。

构造方法如下所示:

方法说明
PriorityQueue()创建一个默认的初始容量 PriorityQueue(11),命令其元素按其自然排序(从小到大,默认出来就是小根堆)
PriorityQueue(Collection<? extends E> c)创建一个 PriorityQueue包含在指定集合的元素(c的元素都被放置到优先级队列集合)。
PriorityQueue(Comparator<? super E> comparator)创建一个默认的初始容量和它的元素是按指定的比较器 PriorityQueue
PriorityQueue(int initialCapacity)创建一个具有指定的初始容量,命令其元素按其自然排序PriorityQueue
PriorityQueue(int initialCapacity, Comparator<? super E> comparator)创建一个具有指定的初始容量,命令其元素根据指定的比较器 PriorityQueue
PriorityQueue(PriorityQueue<? extends E> c)创建一个 PriorityQueue包含指定的优先级队列中的元素。
PriorityQueue(SortedSet<? extends E> c)创建一个 PriorityQueue含有指定排序的集合中的元素。

基本规则

一个优先队列不允许 null元素。依靠自然排序的优先级队列也不允许插入不可比较的对象(这样做可能导致 ClassCastException)。

此队列的头相对于指定的排序是最小的元素。如果多个元素被绑定为最小值,头部是这些元素的一个,关系被任意地打破。队列检索操作poll,remove,peek,和element可以访问在队列的头元素。

优先队列是无界的,但有一个内部容量管理用于存储在队列中的元素的数组的大小。它总是至少和队列大小一样大。当元素被添加到优先级队列中时,它的容量会自动增长。

这个类和它的迭代器实现所有的可选方法的Collection和Iterator接口。方法iterator()提供的迭代器不能保证遍历优先级队列的元素在任何特定的顺序。如果你需要有序遍历,考虑使用Arrays.sort(pq.toArray())

注意:此实现不同步。多线程不能访问PriorityQueue实例同时如果线程修改队列。相反,使用线程安全类PriorityBlockingQueue

时间复杂度

  • 该类实现了O(log(n))的方法(如:offer、poll、remove()和add方法);
  • 对于remove(Object)和contains(Object)方法是线性时间O(n)
  • 对检索方法的时间为常数O(1)(如:peek,element,和size)

方法API

PriorityQueue本质上还是队列,是java实现堆排序提供的API接口,我们直接调用即可。

如上图PriorityQueue的继承关系,实现的也是Queue接口,那么他可以队列的方法,队列的基本方法如下:

方法功能
add(Element e)队尾添加元素
clear()清空整个列队
contains(Object o)检查是否包含当前参数元素,返回布尔类型
offer(E e)添加元素
peek()访问队首元素(不删除)
poll()取出队首元素,(删除)
remove(Object o)根据value删除指定元素
size()返回长度
isEmpty()判断队列是否为空,返回布尔类型

除此以外他还有其他的一些方法

入门代码

实现一个小顶堆:

package com.leetcode.www;

import java.util.Comparator;
import java.util.PriorityQueue;

class Solution 
    public static void main(String[] args) 
        // 默认小根堆
        PriorityQueue<Integer> heap = new PriorityQueue<>(new Comparator<Integer>() 
            @Override
            public int compare(Integer o1, Integer o2) 
                return o1 - o2;
            
        );

        // 将数组元素放入堆
        int[] arr = 6,5,2,3,1,4;

        for (int i :arr) 
            heap.offer(i);
        

        System.out.println(heap);
        int index = 0;
        // 每次将堆顶元素放入数组,并删除堆顶元素,相当于每次从剩余堆中取最大值
        for (int i = 0; i < arr.length; i++) 
            arr[index++] = (Integer) heap.poll();
        

        for (int i :arr) 
            System.out.print(i + " ");  // 1 2 3 4 5 6 
        
    

运行结果如下:

深入探究


但是就有一个问题了:PriorityQueue 默认是小根堆,那么怎么实现大顶堆呢?,

答案:大根堆需要重写比较器。对与PriorityQueue,就要借助于comparator比较器,来实现大根堆。

PriorityQueue 实现大顶堆

其中实现方法有两种

第一种:Comparator

重写compare方法

PriorityQueue<Integer> bigHeap=new PriorityQueue<>(new Comparator<Integer>() 
    @Override
    public int compare(Integer o1, Integer o2) 
        return o2-o1;
    
);

第二种:lambda表达式

借助lambda表达式实现:

Queue<Integer> bigHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);

大顶堆:代码案例

我们修改一下上面的默认小顶堆案例

用o2-o1,降序排序就能实现大顶堆了

PriorityQueue<Integer> heap = new PriorityQueue<>(new Comparator<Integer>() 
    @Override
    public int compare(Integer o1, Integer o2) 
        return o2 - o1;
    
);

或者使用lambda表达式

PriorityQueue<Integer> heap = new PriorityQueue<>((o1, o2) -> 
    return o2-o1;
);

运行结果如下:

比较器深入探究

除了比较单列集合类型,也可以比较自定义类型

例如有下面一个自定义类,我们想按照对象的weight进行比较

class Node 
    int node;
    int weight;

    Node(int x, int y) 
        this.node = x;
        this.weight = y;
    

    @Override
    public String toString() 
        return "Node" +
                "node=" + node +
                ", weight=" + weight +
                '';
    


可以这样写:

PriorityQueue<Node> queue1 = new PriorityQueue<>((o1, o2) -> 
    return o1.weight - o2.weight > 0 ? 1 : -1;
);
Node n1 =new Node(1, 2);
Node n2 =new Node(1, 1);
Node n3 =new Node(1, 3);
queue1.offer(n1);
queue1.offer(n2);
queue1.offer(n3);

for (Node node : queue1) 
    System.out.println(node.toString());

运行结果如下:

如果想按照weight降序,需要将返回值的 1 和 -1 换一下位置

PriorityQueue<Node> queue = new PriorityQueue<>(
        (a, b) -> (a.weight - b.weight > 0 ? -1 : 1) //lamda表达式,compartor如果顺序对返回1,如果顺序不对返回-1
);

补充知识:compare返回值

  • 如果返回值为负数,表示当前存入的元素是较小值,存左边(从大到小)
  • 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
  • 如果返回值为正数,表示当前存入的元素是较大值,存右边(从小到大)

我也发现了一个规律:第一个数减第二个数就是升序,用第二个数减第一个数就是降序

解决实际问题

力扣:出现频率最高的 k 个数字

题目链接:https://leetcode.cn/problems/g5c51o/


按照小顶堆的思想,每次都比较放进去让它自动排序就好了

解决代码:

class Solution 
    public int[] topKFrequent(int[] nums, int k) 
        // 存一个出现频率map
        Map<Integer,Integer> hashMap = new HashMap<Integer,Integer>();
        for(int num:nums)
            hashMap.put(num,hashMap.getOrDefault(num,0)+1);
        

        // int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        // 小根堆
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(
            new Comparator<int[]>()
                public int compare(int[] o1, int[] o2) 
                    return o1[1]-o2[1];
                
            
        );

        // 这个小顶堆里放的就是那前k个最大的  来一个一比  新来的与堆里最小的比  如果大就放进去  如果小那么就说明已经不是前k个了
        for(Map.Entry<Integer,Integer> entry:hashMap.entrySet())
            int num = entry.getKey();
            int count = entry.getValue();
            // 如果到达k容量了
            if(queue.size()==k)
                if(count>queue.peek()[1])
                    queue.poll();
                    queue.offer(new int[]num,count);
                
            else
                queue.offer(new int[]num,count);
            
        

        // 遍历输出
        int[] result=new int[k];
         for (int i = 0; i < k; ++i) 
            result[i] = queue.poll()[0];
        

        return result;

    

以上是关于Java:PriorityQueue详解——从0基础到真应用的主要内容,如果未能解决你的问题,请参考以下文章

PriorityQueue详解

Java的优先队列PriorityQueue详解

Java优先队列(PriorityQueue)和有序集合(TreeSet)

深入理解Java PriorityQueue

java - 如何使用PriorityQueue集合从Java中的给定数组在O(n)时间内构建最大堆?

Java - PriorityQueue