队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列 ,在该场景中,队列显然不适用, 这时我们需要的操作是,一是返回最高优先级对象,二是添加新对象.这种数据结构称作优先级队列(Priority Queue)。
在JDK1.8中的Priority Queue底层使用了堆这种数据结构 ,而堆实际就是在完全二叉树的基础上进行一些调整。
如果有一个集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
堆是一颗完全二叉树,可以利用层序遍历的规则采用顺序的方式来高效存储
对于非二叉树,则不适合使用顺序排序方式进行存储,会导致空间利用率比较低
将元素存储到数组中后,利用完全二叉树的性质对树进行还原,假设i为数组下标,有以下性质
对于集合{ 27,15,19,18,28,34,65,49,25,37 } 中的数据,首先利用层序遍历规则对树进行还原
此时树已经满足堆的性质了,只要进行向下调整即可
这里我们以大根堆为例:
1.parent记为需要调整的根节点,child记为parent根节点的左孩子
2.parent存在左孩子,即:child
- parent右孩子是否存在,存在找的左右孩子中较大的孩子,并将child修改为较大孩子的下标
- 将parent与较大的孩子child比较
- parent大于child,调整结束
- 否则,交换parent与child,交换结束后,parent中小的元素向下移动,可能导致树不能满足堆的结构,因此继续向下调整,即parent=child;child=parent*2+1;进行操作2.
private void shiftDown (int parent) {
if (parent < 0) {
return;
}
// 1. 先去找到左孩子节点
int child = parent * 2 + 1;
while (child < size) {
// 2. 判断有没有右孩子
if (child + 1 < size) {
// 有右孩子节点,如右孩子节点的值比左孩子节点的值大,那么child就设置成右孩子节点的下标
if (elementData[child + 1] > elementData[child]) {
child++; // 这时child下标对应的值是左右孩子节点中最大的那个
}
}
// 3. 用孩子节点中最大的值与父节点去比较
if (elementData[parent] >= elementData[child]) {
break;
}
// 4. 交换父子节点的值
swap(elementData, parent, child);
// 5. 向下调整,重置父子下标
parent = child;
child = (parent * 2) + 1;
}
}
private void swap(int[] elementData, int parent, int child) {
int tmp=elementData[parent];
elementData[parent]=elementData[child];
elementData[child]=tmp;
}
核心为对集合进行向下调整
public Head(int []array) {
//复制数组,防止array的修改影响elementData
this.elementData = Arrays.copyOf(array, array.length);
this.size = elementData.length;
//向下调整为堆结构
int parent=(size-1-1)/2;
for (int i = parent; i >=0 ; i--) {
shiftDown(i);
}
}
- 将元素放入到底层空间中(需判断是否需要扩容)
- 将最后插入的节点向上调整(与向下调整原理相似),直到符合堆结构
public void offer(int val){
//判断数组是否满了
if (isFull()){
//2倍扩容
elementData=Arrays.copyOf(elementData,DEFAULT_CAPACITY*2);
}
//新元素插入到最后一个位置
elementData[size]=val;
size++;
shiftUp(size-1);
}
//向上调整
private void shiftUp(int child) {
//判断是否越界
if (child>=size){
return;
}
int parent=(child-1)/2;
//向上调整
while (child>0&&parent>=0){
//和父节点的值比较大小
if(elementData[child]<=elementData[parent]){
break;
}
//此时孩子节点的值比父节点的值大,交换
swap(elementData,parent,child);
//重置父子下标
child=parent;
parent=(child-1)/2;
}
}
private boolean isFull() {
return size==elementData.length;
}
堆的删除实质是删除堆顶元素。
- 将堆顶元素与最后一个元素交换
- 将堆中有效数据个数减少一个
- 对堆顶元素进行向下调整
public int poll(){
//判断数组是否为空
if (isEmpty()){
throw new RuntimeException("数组为空");
}
int val=elementData[0];
swap(elementData,0,size-1);
size--;
shiftDown(0);
return val;
}
private boolean isEmpty() {
return size==0;
}
public class Head {
//用一个数组存放数据
private int[] elementData;
//数组元素个数
private int size;
//数组大小
private int DEFAULT_CAPACITY=10;
public Head() {
this.elementData=new int [DEFAULT_CAPACITY];
this.size=0;
}
//通过一个数组创建堆
public Head(int []array) {
//复制数组,防 止array的修改影响elementData
this.elementData = Arrays.copyOf(array, array.length);
this.size = elementData.length;
//向下调整为堆结构
int parent=(size-1-1)/2;
for (int i = parent; i >=0 ; i--) {
shiftDown(i);
}
}
// 向下调整的核心逻辑
private void shiftDown (int parent) {
if (parent < 0) {
return;
}
// 1. 先去找到左孩子节点
int child = parent * 2 + 1;
while (child < size) {
// 2. 判断有没有右孩子
if (child + 1 < size) {
// 有右孩子节点,如右孩子节点的值比左孩子节点的值大,那么child就设置成右孩子节点的下标
if (elementData[child + 1] > elementData[child]) {
child++; // 这时child下标对应的值是左右孩子节点中最大的那个
}
}
// 3. 用孩子节点中最大的值与父节点去比较
if (elementData[parent] >= elementData[child]) {
break;
}
// 4. 交换父子节点的值
swap(elementData, parent, child);
// 5. 向下调整,重置父子下标
parent = child;
child = (parent * 2) + 1;
}
}
private void swap(int[] elementData, int parent, int child) {
int tmp=elementData[parent];
elementData[parent]=elementData[child];
elementData[child]=tmp;
}
/**
* 堆的插入
* @param val
*/
public void offer(int val){
//判断数组是否满了
if (isFull()){
//2倍扩容
elementData=Arrays.copyOf(elementData,DEFAULT_CAPACITY*2);
}
//新元素插入到最后一个位置
elementData[size]=val;
size++;
shiftUp(size-1);
}
//向上调整
private void shiftUp(int child) {
//判断是否越界
if (child>=size){
return;
}
int parent=(child-1)/2;
//向上调整
while (child>0&&parent>=0){
//和父节点的值比较大小
if(elementData[child]<=elementData[parent]){
break;
}
//此时孩子节点的值比父节点的值大,交换
swap(elementData,parent,child);
//重置父子下标
child=parent;
parent=(child-1)/2;
}
}
private boolean isFull() {
return size==elementData.length;
}
/**
* 堆的删除
* @return
*/
public int poll(){
//判断数组是否为空
if (isEmpty()){
throw new RuntimeException("数组为空");
}
int val=elementData[0];
swap(elementData,0,size-1);
size--;
shiftDown(0);
return val;
}
private boolean isEmpty() {
return size==0;
}
public int peek(){
return elementData[0];
}
public void display(){
StringBuilder sb=new StringBuilder();
sb.append("[");
for (int i = 0; i < size; i++) {
sb.append(elementData[i]);
if (i
Java集合框架为我们提供了2种优先级队列的接口:PriorityQueuePriorityBlockingQueue,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的 。
使用PriorityQueue是必须导入相应的包
import java.util.PriorityQueue;
PriorityQueue中放置的元素必须能够比较大小,否则抛出ClassCastException异常
插入对象不能为null,否则抛出NullPointerException异常
内部默认容量为11,空间不足时会进行扩容
默认情况下是小根堆,如需要大根堆需要用户提供比较器
构造方式
构造器 | 介绍 |
---|---|
PriorityQueue() | 创建一个具有默认初始容量(11)的PriorityQueue,根据元素的自然顺序对其进行排序 |
PriorityQueue(int initialCapacity) | 创建具有指定初始容量的PriorityQueue,根据元素的自然顺序对其排序 |
PriorityQueue(Comparator super E> comparator) | 用一个集合来创建优先级队列 |
插入/删除/获取优先级最高的元素
函数名 | 介绍 |
---|---|
boolean offer(E e) | 插入元素e,插入成功返回true,如果e为空,抛出NullPointerException异常,空间不足时会进行扩容 |
E peek() | 获取优先级最高的元素,如果优先级队列为空,返回null |
E poll() | 移除优先级最高的元素并返回,如果优先级队列为空,返回null |
int size() | 获取有效元素的个数 |
void clear() | 清空 |
olean offer(E e) | 插入元素e,插入成功返回true,如果e为空,抛出NullPointerException异常,空间不足时会进行扩容 |
| E peek() | 获取优先级最高的元素,如果优先级队列为空,返回null |
| E poll() | 移除优先级最高的元素并返回,如果优先级队列为空,返回null |
| int size() | 获取有效元素的个数 |
| void clear() | 清空 |