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基础到真应用的主要内容,如果未能解决你的问题,请参考以下文章
Java优先队列(PriorityQueue)和有序集合(TreeSet)