有两个最基本操作,一是返回最高优先级对象,二是添加新对象,这种数据结构就叫做优先级队列。
通过堆这种数据结构可以实现优先级队列,堆其实就是对完全二叉树进行了一些调整。
堆分为小根堆和大根堆,如果一颗完全二叉树,其左右节点均大于根结点,就叫做小根堆;
如果左右节点均小于根结点,就叫做大根堆。
(1)堆总是一颗完全二叉树。
(2)堆的某个节点总是大于或小于父亲结点的值。
由于堆是一颗完全二叉树,因此我们可以用层序遍历的方式来实现高效存储。
调整过程(以大根堆为列):
(1)让parent标记需要调整的节点,child标记parent的左孩子;
(2)如果parent的左孩子存在,即:child < size, 进行以下操作,直到parent的左孩子不存在
[1] parent右孩子是否存在,存在找到左右孩子中最大的孩子,让child进行标记
[2]将parent与较小的孩子child比较,如果parent大于较小的孩子child,调整结束,否则,交换parent与较小的孩子child,交换完成之后,parent中大的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent = child;child = parent*2+1; 然后继续(2);
public class MyPriorityQueue {
public int[] elem;
public int usedSize;
public MyPriorityQueue(){
this.elem=new int[10];
}
//传一个数组,对堆初始化
public void initElem(int[] array){
for(int i=0;i<array.length;i++){
this.elem[i]=array[i];
//每插入一个数据,usedSize++
this.usedSize++;
}
}
public void createPriorityQueue( ){
//计算出最后一颗子树的父亲节点并用parent标记,然后
//通过parent--对每棵子树进行调整,直到parent<0
for (int parent=(this.usedSize-1-1)/2;parent>=0;
parent--) {
//向下调整
shiftDown(parent,this.usedSize);
}
}
//向下调整
private void shiftDown(int parent, int usedSize) {
int child=2*parent+1;
while(child<usedSize){
//找到左右孩子最大值
if(child+1<usedSize && elem[child]<elem[child+1]){
child++;
}
if(elem[child]>elem[parent]){
int tmp=elem[child];
elem[child]=elem[parent];
elem[parent]=tmp;
parent=child;
child=2*parent+1;
}else{
//说明当前树已经调整完了
break;
}
}
}
建堆的时间复杂度为O(N)
public void offer(int val){
if(isFull()){
elem= Arrays.copyOf(elem,
2*elem.length);
}
elem[usedSize]=val;
int child=usedSize;
int parent=(child-1)/2;
usedSize++;
while(parent>=0){
if(elem[parent]<elem[child]){
int tmp=elem[parent];
elem[parent]=elem[child];
elem[child]=tmp;
child=parent;
parent=(child-1)/2;
}else {
break;
}
}
}
图示如下(图中是以小根堆为例)
public int poll(){
//先判断队列是否为空
if(isEmpty()){
return -1;
}
int val=elem[0];
swap(Collections.singletonList(elem),
0,usedSize-1);
shiftDown(0,usedSize-1);
usedSize--;
return val;
}
public boolean isEmpty(){
return usedSize==0;
}
图示:
static void TestPriorityQueue() {
// 创建一个空的优先级队列,底层默认容量是11
PriorityQueue<Integer> q1 = new PriorityQueue<>();
// 创建一个空的优先级队列,底层的容量为initialCapacity
PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
ArrayList<Integer> list = new ArrayList<>();
list.add(4);
list.add(3);
list.add(2);
list.add(1);
// 用ArrayList对象来构造一个优先级队列的对象
// q3中已经包含了三个元素
PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
System.out.println(q3.size());
System.out.println(q3.peek());
}
注意:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器
// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
public class TestPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
p.offer(4);
p.offer(3);
p.offer(2);
p.offer(1);
p.offer(5);
System.out.println(p.peek());
}
}
static void TestPriorityQueue2(){
int[] arr = {4,1,9,2,8,0,7,3,6,5};
// 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
// 否则在插入时需要不多的扩容
// 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
for (int e: arr) {
q.offer(e);
}
System.out.println(q.size()); // 打印优先级队列中有效元素个数
System.out.println(q.peek()); // 获取优先级最高的元素
// 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
q.poll();
q.poll();
System.out.println(q.size()); // 打印优先级队列中有效元素个数
System.out.println(q.peek()); // 获取优先级最高的元素
q.offer(0);
System.out.println(q.peek()); // 获取优先级最高的元素
// 将优先级队列中的有效元素删除掉,检测其是否为空
q.clear();
if(q.isEmpty()){
System.out.println("优先级队列已经为空!!!");
}
else{
System.out.println("优先级队列不为空");
}
}
注意:以下是JDK 1.8中,PriorityQueue的扩容方式:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
代码示例:
public void headSort(){
int end=usedSize-1;
while(end>0){
swap(Collections.singletonList(elem),0,end);
shiftDown(0,end);
end--;
}
}