1.什么是优先队列
队列是一种特殊的线性表,其插入和删除的过程分别在线性表的两端进行。向队列中插入元素的过程称为入队,删除元素的过程为出队,入队的一端称为对尾,出队的一段称为队头。因为插入和删除分别是在队尾和队头进行的,所以队列的特点是先进先出(FIFO)。队列的主要操作有:
优先队列继承了队列的接口,但是一种特殊的队列。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。不管队列里的元素是怎样的,在出队是时候,出队的永远都是比较级最大的元素。这个时候如果还采用原来数组和链表结构的话,每次出队都要通过遍历所有元素来取出比较级最高的元素,这样时间复杂度为O(n),效率太低了,所以我们采用一种新的结构——堆来处理。
2.什么是堆
堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆和普通树的区别
堆看上去和树一样,它们之间有相似之处也有一些不同。我们来看一下两者的主要差别:
节点的顺序。在二叉搜索树中,左子节点必须比父节点小,右子节点必须必比父节点大。但是在堆中并非如此。在最大堆中两个子节点都必须比父节点小,而在最小堆中,它们都必须比父节点大。
内存占用。普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配额为是我内存。堆仅仅使用一个数据来村塾数组,且不使用指针。当前节点的左子节点 =当前节点的索引 * 2 ,当前节点的右子节点 = 当前节点的索引 + 1,相当于树的层次遍历
平衡。二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(log n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足对属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(log n) 的性能。
搜索。在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作。
代码实现
下面通过构建一个堆来实现优先队列
import java.util.ArrayList; //借用java自带的动态数组来避免写扩展代码
import java.util.Collection;
public class MaxHeap<E extends Comparable<E>> { //继承比较接口,可以比较自定义的数据类型
private ArrayList<E> data;
public MaxHeap(int capacity){
data = new ArrayList<>(capacity);
}
public MaxHeap(){
data = new ArrayList<>();
}
public MaxHeap(Collection<E>arr){ //把集合通过构造方法转换成最大堆
data = new ArrayList<E>(arr);
for(int i = parent(arr.size() - 1) ; i >= 0 ; i --)
siftDown(i);
}
// 返回堆中的元素个数
public int size(){
return data.size();
}
// 返回一个布尔值, 表示堆中是否为空
public boolean isEmpty(){
return data.isEmpty();
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
private int parent(int index){
if(index == 0)
throw new IllegalArgumentException("index-0 doesn't have parent.");
return (index - 1) / 2;
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
private int leftChild(int index){
return index * 2 + 1;
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
private int rightChild(int index){
return index * 2 + 2;
}
// 向堆中添加元素
public void add(E e){
data.add(e); //加在末尾
siftUp(data.size() - 1);
}
private void siftUp(int k){ //元素上浮,就是比较结点与父节点的大小,如果大于父节点就交换位置
while(k > 0 && data.get(parent(k)).comp