优先级队列依旧是队列,依旧遵循着先进先出的原则,不过和队列所不一样的是,优先级队列操作的数据有优先级,优先级高的数据先出队列,优先级队列底层所使用的数结构便是堆
目录
1.堆
1.1堆的概念
我们可将堆看成是一颗完全二叉树按照层序遍历的方式将数据放在一个一维数组中构成的,堆又有大根堆和小根堆之分
大根堆:父节点的值不小于子节点的值称之为大堆,根节点的值为堆的最大值时称为“大根堆”
小根堆:父节点的值不大于子节点的值称之为小堆,根节点的值为堆的最小值时称为“小根堆”
下面依旧模拟实现一个堆
1.2创建堆
以大根堆为例,二叉树以下图为例
上面的二叉树并不是按照大根堆来进行排序的,所以我们需要对其进行调整
1.按照层序遍历的方式找到最后一颗子树的根节点开始进行调整,图中就是下标为4的位置开始,从4开始依次往前
2.每一颗子树都是从根节点开始往下进行调整
具体的调整过程如图
那么现在有两个问题:
1.如何确定最后一颗子树的根节点?
2.如何判断子树是否调整完成?
假设树中节点个数为len,那么最后一个节点的下标就是len-1,按照二叉树性质,最后一个节点的父节点下标就是(len-1-1)/2,同时len也能作为判断子树是否调整完成的依据(所有节点的下标都不能超过len)
代码如下:
public int[] elem;
public int usedSize;
public Pile() {
this.elem = new int[10];
}
public void create(int[]array) {
for (int i = 0; i < array.length; i++) {
elem[i]=array[i];
usedSize++;
}
for (int j = (usedSize-1)/2; j >=0 ; j--) {
shiftDown(j,usedSize);
}
}
private void shiftDown(int root, int len) {
int parent=root;
int child=2*parent+1; //此时child是左孩子
while(child<len) {
if(child+1<len && elem[child+1]>elem[child]) {
child++;//右孩子存在且大于左孩子
}
//孩子的最大值和根节点进行比较
if(elem[child]>elem[parent]) {
int i=elem[child];
elem[child]=elem[parent];
elem[parent]=i;
parent=child;//child后面也可能有子树
child=2*parent+1;
} else {
break;
}
}
}
创建完堆后就是堆的操作,这里重点讲堆的插入和删除数据
1.3插入数据
思路也很简单,以上面创建好的大根堆为例
假设要把80插入堆中
首先是将80放在最后的位置(即下标10的位置)
80和其父节点进比较,80>37,二者交换位置,然后80再和新的父节点进行比较,以此类推直至出现大于80的值或者80成为根节点
public void offer(int data) {
if(isFull()) {
//数组已满,进行扩容
elem= Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize]=data;
shiftUp(usedSize);
usedSize++;
}
private boolean isFull() {
return usedSize==elem.length;
}
private void shiftUp(int child) {
int parent=(child-1)/2;
while(child>0) {
if(elem[child]>elem[parent]) {
int i=elem[child];
elem[child]=elem[parent];
elem[parent]=i;
child=parent;
parent=(child-1)/2;
} else {
break;
}
}
}
1.4删除数据
0下标元素的值和最后一个元素进行互换,有效数据数量-1即可,之后再次调用shiftDown保证其依旧是大根堆
public void poll() {
if(isEmpty()) {
return;
}
int i=elem[0];
elem[0]=elem[usedSize-1];
elem[usedSize-1]=i;
usedSize--;
shiftDown(0,usedSize);
}
private boolean isEmpty() {
return usedSize==0;
}
2.Java的优先级队列
Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,这里主要介绍PriorityQueue
2.1PriorityQueue的特性
1.PriorityQueue所在的包为 java.util.PriorityQueue
2.PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常
之前说过如何实现对象之间的比较,这里再重复一遍
- 重写equals方法,但只能比较两个对象是否相等
- 实现compareable接口,重写里面的compareTo方法
- 实现comparator接口,重写compare方法(比较器)
3.PriorityQueue不能插入null对象,否则会抛出NullPointerException
4.PriorityQueue默认情况下是小根堆,如果想要大根堆的话需要自己写比较器5.PriorityQueue初始默认容量为11,可扩容
6.PriorityQueue插入和删除元素的时间复杂度为
PriorityQueue常用的方法如下:
boolean offer(E e) |
插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时 间复杂度 ,注意:空间不够时候会进行扩容 |
E peek() | 获取优先级最高的元素,如果优先级队列为空,返回null |
E poll() | 移除优先级最高的元素并返回,如果优先级队列为空,返回null |
int size() | 获取有效元素的个数 |
void clear() |
清空 |
boolean isEmpty() |
检测优先级队列是否为空,空返回true |
优先级队列的内容到这就结束,完