API
优先队列是一种抽象数据类型,最重要的操作就是删除最大元素(delMax()
)和插入元素(insert()
)。
初级实现
数组实现
- 无序
删除最大元素需要加入排序算法 - 有序
insert() 将所有最大元素向右移一格以保证数组有序
链表实现
- 在表头以O(1)执行insert(),并遍历该链表以删除最大元素(O(N))
- 始终保持链表的排序状态,这样insert()操作代价高昂(O(N))
二叉堆(堆)
二叉堆 能够很好的实现优先队列的基本操作
定义 当一颗二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序。
在堆有序的二叉树中,每个结点都小于等于它的父结点。从任意结点向上,我们都能得到一列非递减的元素;从任意结点向下,我们都能得到一列非递增的元素。
根结点是堆有序的二叉树中的最大结点
二叉堆表示法
二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级存储(不使用数组的第一个元素)
在一个堆中,位置k的结点的父结点的位置为[k/2],而它的两个子结点的位置分别为2k和2k+1。
堆算法
用长度为N+1的私有数组pq[]来表示一个大小为N的堆(pq[0]不用)
辅助方法:
/**
* 比较元素的大小
* @param i
* @param j
* @return
*/
private boolean less(int i,int j){
return pq[i].compareTo(pq[j]) < 0;
}
/**
* 交换两个元素
* @param i
* @param j
*/
private void exch(int i,int j){
Key t = pq[i];
pq[i] = pq[j];
pq[j] = t;
}
由下至上的堆有序化(上浮)
/**
* 如果堆有序的状态因为某个结点变得比它的父结点更大而被打破,
* 我们需要通过交换它和它的父结点来修复
* 位置为k的结点,它的父结点的位置为[k/2]
* @param k
*/
private void swim(int k){
while (k > 1 && less(k/2,k)){
exch(k/2,k);
k = k/2;
}
}
由上至下的堆有序化(下沉)
/**
* 如果堆有序的状态因为某个结点变得比它的两个子结点或是其中之一更小了而被打破,
* 我们可以通过将它和它的两个子结点中的较大者来恢复堆
* 交换可能会继续打破堆有序的状态,需要不断的用相同的方式将其修复
*
* 位置为k的结点,它的子结点的位置为[2k]或[2k+1]
* @param k
*/
private void sink(int k){
while (2*k <= N){
int j = 2*k;
if (j < N && less(j,j+1)) j++;
if (!less(k,j)) break;
exch(k,j);
k = j;
}
}
插入元素insert()
将新元素加到数组末尾,增加堆的大小并让这个新元素上浮到合适的位置。
删除最大元素delMax()
从数组顶端删去最大的元素并将数组最后一个元素放到顶端,减小堆的大小并让这个元素下沉到合适的位置
基于堆的优先队列
优先队列由一个基于堆的完全二叉树表示,存储于数组pq[1..N]
中,pq[0]没有使用。
在insert()
中,将N加1并把新元素添加在数组最后,然后用swim()
恢复堆的秩序。
在delMax()
中,从pq[1] 中得到需要返回的元素,然后将pq[N]移到pq[1],将N减1并用sink()
恢复堆的秩序。同时我们还将不再使用的pq[N+1]设为null
,以便系统回收它所占用的空间。