第六章 优先队列

  • 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 基本的堆操作

  1. 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;
}
  1. deleteMin
    找到最小元很容易,但是删除比较困难,做法将X放入沿着从根开始的包含最小儿子的一条路径上的一个正确位置。
    (代码和图略)

6.3.4 堆的其他操作

  1. decreaseKey
    decreaseKey(p, ▲)操作减小在位置p处的元素的值,减小的幅度为正的量▲。可能会破坏堆序性质,所以必须通过上滤操作进行调整。
  2. increaseKey
    increaseKey(p, ▲)操作增加在位置p处的元素的值,增加的幅度为正的量▲,可以用下滤来完成。
  3. remove
    remove§删除堆中位置p上的结点,首先通过decreaseKey(p, ∞)然后再执行deleteMin()。
  4. buildHeap
    二叉堆通过项的原始集合来构造。

6.4 优先队列的应用

6.4.1 选择问题

输入N个元素以及一个整数k,找出第k个最大的元素。

  1. 算法6A
    先将N个元素读入一个数组,然后对数组应用buildHeap算法,最后执行k次deleteMin操作,之后提取的元素就是正确答案。
    构造堆的最坏情形是O(N),每次执行deleteMin是O(logN)时间。因此总的运行时间是O(N+klogN)
  2. 算法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 二项队列结构

一个二项队列不是一棵堆序的树,而是堆序的树的集合,称为森林。堆序中的每一棵树都是有约束的形式,叫做二项树