优先级队列 和 堆
1.优先级队列
1.1概念
前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但在有些情况下,操作数据可能带有优先级,一般出队列时,可能就需要优先级高的先出队列.
这种情况下,数据结构应该包含两个最基本的操作,一个是返回优先级高的数据,另一个是添加新的数据,这种数据结构就叫做优先级队列(PriorityQueue).
在java中优先级队列(PriorityQueue)的底层是由堆这种数据结构实现的,而堆则是在完全二叉树上做出了调整.
2.优先级队列的模拟实现
2.1 堆的概念
堆是由一组元素的集合,并把所有元素按照完全二叉树的顺序存储方式存储在一维数组中,将根节点最大的堆称为最大堆或者大根堆,根节点最小的堆称为最小堆或者小根堆.
2.2 堆的性质
- 在堆中的某一个节点大于或小于孩子节点的值
- 堆是一颗完全二叉树
2.3 堆的创建
public class MyHeap {
public int[] elme;
public int usedSize;
public MyHeap() {
this.elme = new int[10];
}
//初始化数组
public void initHeap(int[] arrya) {
for (int i = 0; i < arrya.length; i++) {
this.elme[i] = arrya[i];
usedSize++;
}
}
//创建大根堆
public void createBigHeap(int[] arrya){
for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
shiftDownBig(parent,usedSize);
}
}
private void shiftDownBig(int parent,int len) {
int child = 2*parent+1;//找到左孩子
while(child < len) { // 保证最起码一定有左孩子
if(child+1 < len && this.elme[child] < this.elme[child+1]) {
child++; // 一定有右孩子,并且右孩子比左孩子大
}
//判断最大的孩子与parent交换
if (this.elme[child] > this.elme[parent]) {
//孩子大于parent 交换
int tmp = this.elme[child];
this.elme[child] = this.elme[parent];
this.elme[parent] = tmp;
parent = child; //继续向上调整
child = 2*parent+1;
} else {
break;
}
}
}
//创建小根堆
public void createSmallHeap(int[] arrya){
for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
shiftUpSmall(parent,usedSize);
}
}
private void shiftUpSmall(int parent,int len) {
int child = 2*parent+1;//找到左孩子
while(child < len) { // 保证最起码一定有左孩子
if(child+1 < len && this.elme[child] > this.elme[child+1]) {
child++; // 一定有右孩子,并且右孩子比左孩子小
}
//判断最小的孩子与parent交换
if (this.elme[child] < this.elme[parent]) {
//孩子小于parent 交换
int tmp = this.elme[child];
this.elme[child] = this.elme[parent];
this.elme[parent] = tmp;
parent = child; //继续向下调整
child = 2*parent+1;
} else {
break;
}
}
}
}
2.4 堆的插入与删除
2.4.1 堆的插入(小根堆)
堆的插入的步骤:
- 先将需要插入的元素放到最后一个位置,如果空间不够则需要扩容。
- 然后将新插入的元素向上调整,直到满足堆的性质。
//插入元素 时间复杂度为: O(N*logN)
public void offer(int val) {
//判断数组是否为满
if (isFull()) {
this.elme = Arrays.copyOf(this.elme,2*this.elme.length); //二倍扩容
}
this.elme[usedSize++] = val;
shiftUp(usedSize-1);
}
private void shiftUp(int child) {
int parent = (child-1)/2; // 找到父亲节点
while(parent >= 0) {
if (this.elme[child] > this.elme[parent]) { //只需要与根节点比较即可
// 比较
int tmp = this.elme[child];
this.elme[child] = this.elme[parent];
this.elme[parent] = tmp;
child = parent;
parent = (child-1)/2;
}else {
break;
}
}
}
public boolean isFull() {
return usedSize == this.elme.length;
}
2.4.2 堆的删除(小根堆)
堆删除元素的步骤:
- 将根节点与最后一个节点交换
- 将堆中有效数据-1
- 最后调整堆 直到满足为小根堆
//删除元素
public void pop() {
if (isEmpty()) {//数组为空
return;
}
int tmp = this.elme[0];
this.elme[0] = this.elme[usedSize-1];
this.elme[usedSize-1] = tmp;
usedSize--;
shiftUpSmall(0,usedSize);
}
public boolean isEmpty() {
return usedSize == 0;
}
3. PriorityQueue
3.1 特性
java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,本文主要介绍PriorityQueue.
注意:
- PriorityQueue中存放的元素必须是能够比较大小的,不能插入无法比较大小的元素,否则会抛出类型转换异常(ClassCastException).
- 可以插入多个元素,其内部可以自动扩容,最大容量为整形的最大值
- 插入和删除元素的时间复杂度为O(logN)
- PriorityQueue的底层使用了堆这种数据结构
- PriorityQueue默认为小根堆
3.2 构造方法
方法 | 解释 |
---|---|
PriorityQueue() | 创建一个空的优先级队列,默认容量为11 |
PriorityQueue(int initialCapacity) | 创建一个容量为initialCapacity的优先级队列,且initialCapacity不能小于1,否则会抛出异常 |
PriorityQueue(Collection<? extends E> c) | 使用集合来创建优先级队列 |
3.3 常用方法
//插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度 ,注意:空间不够时候会进行扩容
boolean offer(E e)
//获取优先级最高的元素,如果优先级队列为空,返回null
E peek()
//移除优先级最高的元素并返回,如果优先级队列为空,返回null
E poll()
// 获取有效元素的个数
int size()
//清空
void clear()
//检测优先级队列是否为空,空返回true
boolean isEmpty()