学习目标:
目标:熟练掌握Java所学知识
学习内容:
本文内容:优先级队列(堆)
文章目录
一、优先级队列
1.1 概念
队列是一种先进先出的数据结构(FIFO)。但是有些情况下,操作的数据可能带有优先级,出队列时,可能需要优先级高的先出队列,在该场景下,显然不能使用普通队列;
比如: 用手机看视频时,这时有电话打进来,系统就会优先处理打进来的电话
在这种情况下,我们的数据结构应该提供两个基本操作,一个是返回优先级最高的对象,一个是添加新的对象。这种数据结构成为优先级队列
1.2常用接口介绍
1.2.1 PriorityQueue特性
Java集合提供了PriorityQueue和PriorityBlockingQueqe两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的
- 使用时必须导入PriorityQueue所在的包,即
import java.util.PriorityQueue
- PriorityQueue中放的元素必须能够比较大小,不能插入无法比较大小的元素,否则会抛出ClassCastException异常;
- 不能插入null对象,否则会抛出NullPointerException异常;
- 没有容量限制,可以插入任意多个元素,其内部可以自动扩容;
- 插入和删除元素的时间复杂度都是O㏒(2N);
- PriorityQueue底层使用的堆数据结构(堆 后文会介绍到)
1.2.2 PriorityQueue常用接口介绍
- 优先级队列的构造
构造器 | 功能介绍 |
---|---|
PriorityQueue | 创建一个空的优先级队列,默认容量是11 |
PriorityQueue(int initialCapacity) | 创建一个初始容量为initialCapacity大小的优先级队列 |
PriorityQueue(Collection<?extends E>c) | 用一个集合来初始化优先级队列 |
public class TestPriorityQueue {
public static void main(String[] args) {
//创建一个空的优先级队列,默认容量为11
PriorityQueue<Integer> q1=new PriorityQueue<>();
//创建一个空的优先级队列,底层的容量为100
PriorityQueue<Integer> q2=new PriorityQueue<>();
ArrayList<Integer> list =new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//用一个ArrayList对象构造优先级队列
//此时优先级队列已经有3个元素
PriorityQueue<Integer> q3=new PriorityQueue<>(list);
}
}
- 优先级队列的常用方法
函数名 | 功能 |
---|---|
boolean offer(E e) | 插入元素e,插入成功返回true。如果e为空,则抛出NullPointerException异常,时间复杂度为O㏒(2N),空间不够时会自动扩容 |
E peek() | 获取优先级最高的元素,入队优先级队列为空,则返回null |
E poll() | 移除优先级最高的元素并返回,如果优先级队列为空则返回null |
int size() | 获取有效元素个数 |
void clear() | 清空优先级队列 |
boolean isEmpty() | 检测优先级队列是否为空,为空则返回true |
public static void testPriorityQueue2(){
int[] arr={
9,5,2,7,4,8,3,6};
//如果知道元素个数,可以在创建优先级队列时,直接将容量设置好
//否则在插入时会有不必要的扩容操作,效率较低
PriorityQueue<Integer> q=new PriorityQueue<>(arr.length);
//将数组元素插入到优先级队列中
for(int i:arr){
q.offer(i);
}
//获取优先级最高的元素
System.out.println(q.peek());//运行结果 2
//获取优先级队列有效元素个数
System.out.println(q.size());//运行结果 8
q.poll();//删除优先级队列中优先级最高的元素
System.out.println(q.peek());//运行结果 3
System.out.println(q.size());//运行结果 7
q.offer(0);//插入元素0
System.out.println(q.peek());//运行结果 0
}
1.3 优先级队列的应用
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(arr==null||k<=0){
return new int[0];
}
PriorityQueue<Integer> queue=new PriorityQueue<>(arr.length);
//将数组元素插入优先级队列中
for(int i=0;i<arr.length;i++){
queue.offer(arr[i]);
}
int[] res=new int[k];
//得到优先级队列的前k个数
for(int i=0;i<k;i++){
res[i]=queue.poll();
}
return res;
}
}
二、优先级队列的模拟实现
2.1堆的概念
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2.2堆的存储方式
2.3 堆的创建
2.3.1 堆向下调整
这个代码是以小堆为例
public static void shiftDown(int[] arr, int index, int size) {
int parent = index;//需要调整的位置
int child = 2 * parent + 1;//chile标记parent的左孩子节点
while (child < size) {
if (child + 1 < size && arr[child] > arr[child + 1]) {
//判断parent左右孩子的大小关系,让child标记较大的孩子节点
child = child + 1;
}
//判断parent和child位置的元素的大小关系,
//如果arr[child] < arr[parent],则交换位置
if (arr[child] < arr[parent]) {
int tmp = arr[child];
arr[child] = arr[parent];
arr[parent] = tmp;
} else {
//否则arr[parent]就已经找到了合适的位置结束循环
break;
}
parent = child;//更新parent标记的位置
child = 2 * parent + 1;//更新child标记的位置,继续下次循环
}
}
2.3.2 堆的向上调整
同样是以小堆为例
public void shiftUp(int[] arr, int size, int index) {
int child = index;//让child标记要调整元素的位置
int parent = (child - 1) / 2;//child位置的父亲位置
while (child > 0) {
//如果arr[child] < arr[parent],则交换位置
if (arr[child] < arr[parent]) {
int temp = arr[child];
arr[child] = arr[parent];
arr[parent] = temp;
} else {
//否则就找到的index位置元素该在的位置
break;
}
child = parent;//更新child位置,进行下次循环
parent = (child - 1) / 2;//更新parent位置,进行下次循环
}
}
2.3.3删除堆顶元素
删除堆顶元素可直接将堆顶元素与最后一个元素交换,将堆的有效元素个数减一,然后对堆顶元素进行向下调整即可
public Integer poll() {
if (size == 0) {
return null;
}
int res = arr[0];
//交换操作
int temp = arr[0];
arr[0] = arr[size - 1];
arr[size - 1] = temp;
size--;//堆的有效元素个数减一就实现了删除元素
shiftDown(arr, 0, size);//对堆顶元素进行向下调整
return res;
}
2.3.4取堆顶元素
public Integer peek() {
if (size == 0) {
return null;
}
int res = arr[0];
return res;
}