本文将概括性描述优先队列ADT,只对基本的堆数据结构做简要总结,不涉及左式堆,d-堆,斜堆,二项队列等高级数据结构。(算法入门级的总结,欢迎批评指正,根据《数据结构与算法(JAVA语言描述)》一书总结)
优先队列
1.模型
优先队列是至少支持两种操作的数据结构:insert 与 deleteMin。前者等价于队列中的入队,后者等价于出队。
此模型在贪婪算法与外部排序中有较多应用。
2.二叉堆
二叉堆是优先队列的普遍实现。
堆的两个性质:结构性和堆序性。
结构性:堆是一棵被完全填满的二叉树,底层可能是例外,从左到右插入。(完全二叉树)
一棵高为h的完全二叉树有2^h到2^(h+1) - 1个节点,即完全二叉树的高是小于logN的最大整数。
因为完全二叉树的规律性,所以他可以用数组代替链来表示。数组是对二叉树广度优先遍历的结果(数组从角标1开始存值,留下0为空位方便其他操作使用)。
堆序性:即让操作快速执行的性质,即任一节点应该小于它的所有后裔。在一个堆中,对于每一个节点X,X的父亲中的关键字小于或等于X中的关键字,根节点除外。由于此性质findMin的操作花费为O(1)。
3.基本堆操作
首先所有的工作都必须满足堆序性质。
1.insert插入
为插入一个新元素X,我们可以在下一个可使用位置创建空穴(保证该堆为完全树,即结构性),如果X可以放在该空穴中而不破坏堆序性,那么插入完成,否则就将该堆父节点的元素移入该空穴中(即空穴深度减一)继续上述判断过程直到X放入空穴中不破坏堆序性质为止。这种操作一般称之为上滤。
上滤方法代码如下
public void insert(AnyType x)
{
//判断是否到达数组容量上限
if(currentSize == array.length - 1)
enlargeArray(array.length - 1);
//上滤操作
int hole = ++currentSize;
for(array[0] = x;x.compareTo(array[hole/2]) < 0;hole /= 2)
//将父节点放在空穴处,空穴上移
array[hole] = array[hole/2];
array[hole] = x;
}
其实如果直接将X放入空穴反复执行与父节点的插入操作也可以完成上滤操作,可是上滤一层需要交换3次。而这里的方法只需要1次,最后再加一次赋值操作即可。至于对array[0]的赋值操作是为了保证循环的终止,假设新插入元素一直为最小值,则会被上滤到根处,这样compareTo的比较结果为0,可退出循环且此时hole值应为1.
insert操作的最坏运行时间为O(logn)平均时间花费为O(1);
2.deleteMin(删除最小元)
其方法与插入操作有相似之处,因为要去除一个元素,所以我们现将堆中的最后一个元素X删除并记录它的值。然后在根节点处建立空穴,如果最后一个元素X可以放入空穴则deleteMin操作完成,否则比较他的两个儿子的大小,将小的儿子放到根节点处,重复这一过程直到X可以放入空穴或空穴被放入最后一层。然后将记录的值填入空穴完成deleteMin操作。
注意:在比较两个儿子大小时要确保两个儿子的存在,需要通过附加测试来确定是否存在一个或两个儿子。这种测试只会在倒数第二层生效。(因为完全二叉树的特性)。代码省略。
deleteMin操作的最坏运行时间为O(logn)因为来自底层的元素X几乎都被过滤到底层,所以平均运行时间为O(logn)。
4.其他的堆操作
descreaseKey(降低关键字的值)
descreaseKey(p,x)操作降低在位置p处的值,幅度为x,上滤进行调整。
管理员可以通过此方法使他们的程序优先运行
increaseKey(增加关键字的值)
方法与上述增加方法作用相反,许多调度程序自动降低正在过多消耗CPU时间的进程的优先级
delete(删除)
delete(p)删除堆中位置为p的节点。该操作通过执行descreaseKey(p,无限大)然后执行deleteMin来完成。
buildHeap(构建堆)
由于insert操作平均花费O(1),buildHeap即对一个N项集合进行N次insert操作,所以buildHeap的运行时间应该为O(n)平均时间而不是O(nlogn)最坏运行时间(证明过程太过复杂,省略)。
5.优先队列的应用
1.选择问题
关于在N个元素中找出第k个最大元素的选择问题。
对于传统排序算法,该问题较为聪明的解法是:
现将k个元素读入数组并排序,这些元素的最小者在第
k个位置上,然后依次处理剩下的元素,如果大于第k个元素则将其放入数组中正确的位置并将原来的第k个元素除去。算法结束时第k个元素即为解,此方法运行时间为O(N*k)(N^2 - (N - K) * K).如果k正好为N的一半则是这个问题最复杂的解答O(N^2)。(因为任意的k可以对称)。即求中位数,正好这是我们用的最频繁的一种情况。
通过buildHeap可以将此最坏运行时间降至O(NlogN)
为简单起见,我们找出第k个小的元素,我们先构建一个堆O(n)然后执行k 次deleteMin操作,每次用时O(logn)因此此算法在k比较小的情况为O(N)最坏运行时间为O(nlogn)。
对于原问题:求第k个大的元素,我们可以先构建拥有K个元素的堆S,然后执行数次insert与deleteMin操作。。。
over