1.概念
队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。
在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。
2.优先级队列的模拟实现
JDK1.8中的PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整。
3.堆的概念
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一
个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大
堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
4.堆的存储方式
1.堆是一颗完全二叉树,可以采用层序的规则去顺序存储.
对于非完全二叉树,则不适合顺序存储的方式进行存储,因为为了还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低
2.数组的下标映射(可以找规律去推理)
数组下标从0 开始,0 到 n - 1,n为节点数
如果i 为0 , i下标只能表示根节点;
如果 2 * i + 1 < n, 则节点 i 的左孩子是 2 * i + 1,如果不在节点范围内,没有左孩子
如果 2 * i + 2 < n, 则节点 i 的右孩子是 2 * i + 2,如果不在节点范围内,没有右孩子
如果i不为0, i下标表示孩子节点,双亲节点是(i - 2) / 2;
5.堆的创建
1.堆的向下调整和创建
这里是建立小根堆,把整个数组当成一颗二叉树, 依次从最后一颗子树的根节点到最上面的第一颗子树,开始向下调整.(此过程需要循环)
对于一颗子树而言,向下调整过程是
parent指向双亲节点,child指向孩子节点,不确定左还是右,size是指树的节点总数
(1) 让parent标记需要调整的节点,child标记parent的左孩子(parent如果有孩子一定是先有左孩子)
(2)如果parent的左孩子存在,即child < size , 进行以下操作,直到parent没有左孩子
1.如果parent右孩子存在,找到左右孩子最小的孩子,让child指向
2.parent与较小的孩子child比较,
如果parent < child ,调整结束,子树后面的节点都已经满足了小根堆的性质了
如果parent >= child ,交换parent和child ,交换完成之后;由于parent中大的元素向下移动了,子树后面的结构可能不满足堆的性质,因此需要继续向下调整,即parent = child ; child = parent * 2 + 1; 然后重复步骤(2)
import java.util.Arrays;
public class Heap {
private int[] elem;
private int size;
public Heap(int[] array) {
this.elem = array;
this.size = array.length;
}
public void CreateHeap() {
for (int i = (elem.length - 2) / 2; i >= 0; i--) {
shiftDown(i);
}
}
public void shiftDown(int parent) {
int child = 2 * parent + 1;
while (child < size) {
if (child + 1 < size && elem[child + 1] < elem[child]) {
child = child + 1;
}
if (elem[parent] < elem[child]) {
break;
}
int temp = elem[parent];
elem[parent] = elem[child];
elem[child] = temp;
parent = child;
child = parent * 2 + 1;
}
}
public static void main(String[] args) {
int[] array = {10,91,48,56,23,41};
Heap heap = new Heap(array);
heap.CreateHeap();
System.out.println(Arrays.toString(array));
}
}
注意:在调整以parent为根的二叉树,必须要满足parent的左子树和右子树已经是堆了才可以向下调整.否则最大或者最小元素在最下面一层的情况,交换只能到上一层,无法成为堆.
这就是把整个数组当成一颗二叉树, 依次从最后一颗子树的根节点到最上面的第一颗子树,开始向下调整的原因,确保在调整以parent为根的二叉树,必须要满足parent的左子树和右子树已经是堆了.
2.建堆的时间复杂度
3.堆的插入创建和删除
1.堆的插入
1.先将元素放入到底层空间中(树的最后一层),空间不够需要扩容
2.将最后新插入的节点向上调整,节点向上交换移动,直到满足堆的性质
//小根堆
public void shiftUp(int child) {
//child 是第一个节点,不需要调整
if (child == 0) {
return;
}
int parent = (child - 1) / 2;
while (child > 0) {
if (elem[parent] < elem[child]) {
break;
} else {
int temp = elem[parent];
elem[parent] = elem[child];
elem[child] = temp;
// 小的元素向下移动,可能以parent为孩子的子树不满足堆的性质,因此需要继续向上调整
child = parent;
parent = (child - 1) / 2;
}
}
}
2.堆的删除(都是删除堆顶元素)
1.将堆顶元素和堆中最后一个元素交换
2.将堆中有效数据个数size减少1
3.对现在交换过后的堆顶元素进行向下调整
//删除堆顶元素
public int poll() {
int oldValue = array[0];
array[0] = array[--size];
shiftDown(0);
return oldValue;
}
//取堆顶元素
public int peek() {
return array[0];
}
//插入元素
public void offer(int e) {
array[size++] = e;
shiftUp(size - 1);
}
6.常用集合PriorityQueue介绍
1.特性
Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,本文主要介绍PriorityQueue。
关于PriorityQueue的使用要注意:
1. 使用时必须导入PriorityQueue所在的包,即:import java.util.PriorityQueue;
2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常
3. 不能插入null对象,否则会抛出NullPointerException
4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
5. 插入和删除元素的时间复杂度为
6. PriorityQueue底层使用了堆数据结构
7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素
2.构造方法和常用的方法