二叉堆(binary heap),像二叉查找树一样,堆也有两个性质,即结构性和堆序性。
结构性质
堆是一棵被完全填满的二叉树,有可能的例外是在底层,底层上的元素从左到右填入。这样的树称为完全二叉树(complete binary tree)。
易证明,一棵高为h的完全二叉树有2h-1个节点,高为⌊㏒N⌋。用一个数组表示为
对于数组中任一位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的单元(2i+1)中,它的父亲则在位置⌊i/2⌋上。
因此,这里不仅不需要链,而且遍历该树所需要的操作也极为简单,在大部分机器上运行也非常快。这种实现的唯一问题在于,最大的堆大小需要事先估计,但一般这并不成问题(而且如果需要,可以重新调整大小)。
堆序性质
使操作被快速执行的性质是堆序性质(heap-order property):在一个堆中,对于每一个节点X,X的父亲中的关键字小于(或等于)X中的关键字,根节点除外(它没有父亲)。
根据堆序性质,最小元总可以在根处找到。因此,我们以常数时间得到附加操作findMin。
基本的堆操作
- insert(插入)
为将一个元素X插入到堆中,我们在下一个可用位置创建一个空穴(hole),因为否则该堆将不是完全树。如果X可以放在该空穴中而不破坏堆的序,则插入完成。否则,我们把空穴的父节点上的元素移入该空穴中,这样,空穴就朝着根的方向上冒一步。继续该过程直到X能被放入空穴中为止。这种一般的策略叫作上滤(percolate up)。新元素在堆中上滤直到找到正确的位置。
如果欲插入的元素是新的最小元从而一直上滤到根处,那么这种插入的时间将长达O(logN)。
- deleteMin(删除最小元)
当删除一个最小元时,要在根节点建立一个空穴。由于现在的堆少了一个元素,因此堆中最后一个元素X必须移动到该堆的某个地方。如果X可以被放入空穴中,那么deleteMin完成。不过这一般不太可能,因此我们将空穴的两个儿子中较小者移入空穴,那么就把空穴向下推了一层。重复该步骤直到X可以被放入空穴中。因此,我们的做法是将X置入沿着从根开始包含最小儿子的一条路径上的一个正确的位置。这种一般的策略叫作下滤(percolate down)。
当堆中存在偶数个元素的时候,此时将遇到一个节点只有一个儿子的情况。我们必须以节点不总有两个儿子为前提,因此这就涉及到一个附加的测试。一种极其巧妙的解决方法是始终保证算法把每一个节点都看成有两个儿子。当堆的大小为偶数时在每个下滤开始处,可将其值大于堆中任何元素的标记放到堆的终端后面的位置上。这种操作最坏情形运行时间为O(logN)。
其他的堆操作
- decreaseKey(降低关键字的值)
decreaseKey(p,△)操作降低在位置p处的项的值,降值的幅度为正的量△。由于这可能破坏堆序性质,因此必须通过上滤对堆进行调整。该操作对系统管理程序是有用的:系统管理程序能够使它们的程序以最高的优先级来运行。 - increaseKey(增加关键字的值)
increaseKey(p,△)操作增加在p处的项的值,增值的幅度为正的量△。这可以用下滤来完成。许多调度程序自动地降低正在过多地消耗CPU时间的进程的优先级。 - remove(删除)
remove§操作删除堆中位置p上的节点。该操作通过首先执行decreaseKey(p,∞)然后再执行deleteMin( )来完成。当一个进程被用户中止(而不是正常终止)时,它必须从优先队列中被除去。 - buildHeap(构建堆)
一般的算法是将N项以任意顺序放入树中,保持结构特性,总运行时间平均为O(N)。
实现代码
//优先队列的类接口
#include<vector>
template<typename Comparable>
class BinaryHeap {
public:
explicit BinaryHeap(int capacity = 100) :currentSize{ 0 },array(capacity+1) {
}
explicit BinaryHeap(const std::vector<Comparable>& items)
:array(items.size() + 10), currentSize{ items.size() }
{
for (int i = 0; i < items.size(); ++i)
array[i + 1] = items[i];
buildHeap();
}
bool isEmpty()const {
return currentSize == 0;
}
const Comparable& findMin()const {
return array[1];
}
/**
* 将项x插入,允许重复元
*/
void insert(const Comparable& x) {
if (currentSize == array.size() - 1)
array.resize(array.size() * 2);
//上滤
int hole = ++currentSize;
Comparable copy = x;
array[0] = std::move(copy);
for (; x < array[hole / 2]; hole /= 2)
array[hole] = std::move(array[hole / 2]);
array[hole] = std::move(array[0]);
}
void insert(Comparable&& x) {
if (currentSize == array.size() - 1)
array.resize(array.size() * 2);
//上滤
int hole = ++currentSize;
Comparable copy = std::move(x);
array[0] = std::move(copy);
for (; x < array[hole / 2]; hole /= 2)
array[hole] = std::move(array[hole / 2]);
array[hole] = std::move(array[0]);
}
/**
* 删除最小项
* 如果为空则抛出UnderflowException异常
*/
void deleteMin() {
if (isEmpty())
throw UnderflowException{ };
array[1] = std::move(array[currentSize--]);
percolateDown(1);
}
/**
* 删除最小项并将其放在minItem处
* 若为空则抛出UnderflowException异常
*/
void deleteMin(Comparable& minItem) {
if (isEmpty())
throw UnderflowException{ };
minItem = std::move(array[1]);
array[1] = std::move(array[currentSize--]);
percolateDown(1);
}
void makeEmpty() {
for (int i = 1; i <= currentSize; ++i)
array[i] = 0;
currentSize = 0;
}
private:
int currentSize; //堆中元素的个数
std::vector<Comparable> array; //堆的数组
void buildHeap() {
for (int i = currentSize / 2; i > 0; --i)
percolateDown(i);
}
/**
* 在堆中进行下滤的内部方法
* 空穴是下滤开始处的下标
*/
void percolateDown(int hole) {
int child;
Comparable tmp = std::move(array[hole]);
for (; hole * 2 <= currentSize; hole = child) {
child = hole * 2;
if (child != currentSize && array[child + 1] < array[child])
child++;
if (array[child] < tmp)
array[hole] = std::move(array[child]);
else
break;
}
array[hole] = std::move(tmp);
}
};