优先队列的实现常选用二叉堆,在数据结构中,优先队列通常也是指堆。java
堆的两个性质:数组
结构性:堆是一颗除底层外被彻底填满的二叉树,底层的节点从左到右填入,这样的树叫作彻底二叉树。数据结构
堆序性:因为咱们想很快找出最小元,则最小元应该在根上,任意节点都小于它的后裔,这就是小顶堆(Min-Heap);若是是查找最大元,则最大元应该在根上,任意节点都要大于它的后裔,这就是大顶堆(Max-heap)。app
经过观察发现,彻底二叉树能够直接使用一个数组表示而不须要使用其余数据结构。因此咱们只须要传入一个size就能够构建优先队列的结构(元素之间使用compareTo方法进行比较)。测试
public class PriorityQueue<T extends Comparable<? super T>> {
public PriorityQueue(int capacity) {
currentSize = 0;
array = (T[]) new Comparable[capacity + 1];
}
}
对于数组中的任意位置 i 的元素,其左儿子在位置 2i 上,则右儿子在 2i+1 上,父节点在 在 i/2(向下取整)上。一般从数组下标1开始存储,这样的好处在于很方便找到左右、及父节点。若是从0开始,左儿子在2i+1,右儿子在2i+2,父节点在(i-1)/2(向下取整)。ui
咱们这创建最小堆,即对于每个元素X,X的父亲中的关键字小于(或等于)X中的关键字,根节点除外(它没有父节点)。spa
如图所示,只有左边是堆,右边红色节点违反堆序性。根据堆序性,只须要常O(1)找到最小元。3d
图中演示了18插入的过程,在下一个可用的位置创建空穴(知足结构性),发现不能直接插入,将父节点移下来,空穴上冒。继续这个过程,直到知足堆序性。这样就实现了元素插入到优先队列(堆)中。code
/**
* 插入到优先队列,维护堆序性
*
* @param x :插入的元素
*/
public void insert(T x) {
if (null == x) {
return;
}
//扩容
if (currentSize == array.length - 1) {
enlargeArray(array.length * 2 + 1);
}
//上滤
int hole = ++currentSize;
for (array[0] = x; x.compareTo(array[hole / 2]) < 0; hole /= 2) {
array[hole] = array[hole / 2];
}
array[hole] = x;
}
/**
* 扩容方法
*
* @param newSize :扩容后的容量,为原来的2倍+1
*/
private void enlargeArray(int newSize) {
T[] old = array;
array = (T[]) new Comparable[newSize];
System.arraycopy(old, 0, array, 0, old.length);
}
能够反复使用交换操做来进行上滤过程,但若是插入X上滤d层,则须要3d次赋值;咱们这种方式只须要d+1次赋值。orm
若是插入的元素是新的最小元从而一直上滤到根处,那么这种插入的时间长达O(logN)。但平均来看,上滤终止得要早。业已证实,执行依次插入平均须要2.607次比较,所以平均insert操做上移元素1.607层。上滤次数只比插入次数少一次。
如图所示:在根处创建空穴,将最后一个元素放到空穴,已知足结构性;为知足堆序性,须要将空穴下移到合适的位置。
注意:堆的实现中,常常发生的错误是只有偶数个元素,即有一个节点只有一个儿子。因此须要测试右儿子的存在性。
/**
* 删除最小元
* 若优先队列为空,抛出UnderflowException
*
* @return :返回最小元
*/
public T deleteMin() {
if (isEmpty()) {
throw new UnderflowException();
}
T minItem = findMin();
array[1] = array[currentSize--];
percolateDown(1);
return minItem;
}
/**
* 下滤方法
*
* @param hole :从数组下标hole1开始下滤
*/
private void percolateDown(int hole) {
int child;
T tmp = array[hole];
for (; hole * 2 <= currentSize; hole = child) {
//左儿子
child = hole * 2;
//判断右儿子是否存在
if (child != currentSize &&
array[child + 1].compareTo(array[child]) < 0) {
child++;
}
if (array[child].compareTo(tmp) < 0) {
array[hole] = array[child];
} else {
break;
}
}
array[hole] = tmp;
}
这种操做最坏时间复杂度是O(logN)。平均而言,被放到根处的元素几乎下滤到底层(即来自的那层),因此平均时间复杂度是O(logN)。
优先队列常使用二叉堆实现,本篇图解了二叉堆最基本的两个操做:插入及删除最小元。insert以O(1)常数时间执行,deleteMin以O(logN)执行。相信你们看了以后就能够去看java的PriorityQueue源码了。今天只说了二叉堆最基本的操做,还有一些额外操做及分析下次再说。好比,如何证实buildHeap是线性的?以及优先队列的应用等。
声明:图文皆原创,若有转载,请注明出处。若有错误,请帮忙指出,欢迎讨论;若以为能够,点下推荐支持支持。