第六章 优先队列
- 6.1 模型
- 6.2 一些简单的实现
- 6.3 二叉堆
- 6.3.1 结构性质
- 6.3.2 堆序性质
- 6.3.3 基本的堆操作
- 6.3.4 堆的其他操作
- 6.4 优先队列的应用
- 6.4.1 选择问题
- 6.4.2 事件模拟
- 6.5 d堆
- 6.6 左氏堆
- 6.6.1 左氏堆性质
- 6.7 斜堆
- 6.8 二项队列
- 6.8.1 二项队列结构
6.1 模型
优先队列至少允许下列两种操作的数据结构:insert(插入),deleteMin(删除最小项),他的工作是找出、返回和删除优先队列中最小的元素。前者等价于enqueue入队,后者等价于dequeue出队。
本章主要介绍优先队列在离散时间模拟中的应用。
6.2 一些简单的实现
有几种明显的方法
- 用一个简单链表在表头以O(1)执行插入操作,并遍历该链表以删除最小元,这需要O(N)的时间
- 始终让表处于排序状态,这样插入操作O(N),而删除操作的复杂度为O(1)
- 使用二叉查找树,两种操作的平均运行时间都是O(logN)。
6.3 二叉堆
二叉堆binary heap对于优先队列的使用十分常见,也叫做堆。
堆具有两个性质:结构性质和堆序性质。
6.3.1 结构性质
堆是一棵被完全填满的二叉树,不同的是,如果底层上的元素从左到右填入,这样的树称为完全二叉树complete binary tree
因此,一个堆数据结构将由一个数组和一个代表当前堆的大小的整数组成,下面代码为一个优先队列接口。
template <typename Comparable>
class BinaryHeap
{
public:
explicit BinaryHeap( int capacity = 100);
explicit BinaryHeap(const vector<Comparable> & items);
bool isEmpty() const;
const Comparable & findMin() const;
void insert( const Comparable & x)
void deleteMin();
void deleteMin( Comparable & minItem);
void makeEmpty();
private:
int currentSize;
vector<Comparable> array;
void buildHeap();
void percolateDown(int hole);
}
6.3.2 堆序性质
使操作可以快速执行的性质是堆序性质heap order property
在堆中,对于每一个结点X,X的父亲中的键小于(或等于)X中的键,根节点除外,这样就能保证最小元在根上。
6.3.3 基本的堆操作
- insert
为了将元素X插入堆中,在下一个空闲位置创建一个空穴,但是注意在插入的时候保持树平衡,不能破坏堆序性质。这种策略叫做上滤percolate up
实现代码如下
/*Insert item x, allowing duplicates*/
void insert(const Comparable & x)
{
if(currentSize == array.size() - 1)
array.resize(array.size() * 2)
int hole = ++ currentSize;
for(;hole>1 && x< array[hole/2] ; hole /=2)
array[hole] = array [hole/2];
array[hole] = x;
}
- deleteMin
找到最小元很容易,但是删除比较困难,做法将X放入沿着从根开始的包含最小儿子的一条路径上的一个正确位置。
(代码和图略)
6.3.4 堆的其他操作
- decreaseKey
decreaseKey(p, ▲)操作减小在位置p处的元素的值,减小的幅度为正的量▲。可能会破坏堆序性质,所以必须通过上滤操作进行调整。 - increaseKey
increaseKey(p, ▲)操作增加在位置p处的元素的值,增加的幅度为正的量▲,可以用下滤来完成。 - remove
remove§删除堆中位置p上的结点,首先通过decreaseKey(p, ∞)然后再执行deleteMin()。 - buildHeap
二叉堆通过项的原始集合来构造。
6.4 优先队列的应用
6.4.1 选择问题
输入N个元素以及一个整数k,找出第k个最大的元素。
- 算法6A
先将N个元素读入一个数组,然后对数组应用buildHeap算法,最后执行k次deleteMin操作,之后提取的元素就是正确答案。
构造堆的最坏情形是O(N),每次执行deleteMin是O(logN)时间。因此总的运行时间是O(N+klogN) - 算法6B
任何一个时刻都维持k个最大元素的集合S,读入第k+1个元素时,与第k个最大元素进行比较,设为Sk,Sk是S中最小的元素。先用一个堆实现S,buildHeap将前k个元素以总时间O(k)放入堆中。 处理其他元素的时间为O(1),加上O(logk)时间检测元素是否放入S中。
6.4.2 事件模拟
6.5 d堆
d堆就是所有的结点都有d个儿子,因此二叉堆是2堆。
除了不能执行find意外,堆的实现明显的缺点就是:合二为一。这个操作称为合并merge
6.6 左氏堆
左氏堆leftist heap像二叉堆那样既有结构性质又有堆序性质,不同的是:左氏堆不是理想平衡的,趋于非常不平衡的。
6.6.1 左氏堆性质
零路径长null path length:任一结点X的零路径长定义为从X到一个不具有两个儿子的结点的最短路径长。
结论:任一结点的零路径长比他的诸儿子结点的零路径长的最小值多1.
性质:每一个结点X,左儿子的零路径长至少比与右儿子的零路径长一样大。
定理:在右路径上有r个结点的左氏树必然至少有2r-1个结点
6.7 斜堆
斜堆skew heap是左氏堆的自调节形式,斜堆和左氏堆的关系类似于伸展树和AVL树之间的关系。
6.8 二项队列
6.8.1 二项队列结构
一个二项队列不是一棵堆序的树,而是堆序的树的集合,称为森林。堆序中的每一棵树都是有约束的形式,叫做二项树