中文版 | English
[TOC]
二叉堆(binary heap)
容易证明,一棵高为$h$的完全二叉树有$2^h$到$2^{h+1} - 1$个结点;这意味着,完全二叉树的高是$\lfloor logN \rfloor$,显然它是$O(logN)$。
一棵完全二叉树
完全二叉树的数组实现
定理6.1 包含$2^{h+1} - 1$个结点且高为$h$的理想二叉树(perfect binary tree)的结点的高度和为$2^{h+1} - 1 - (h + 1)$。
证明 容易看出,该树由高度$h$上的1个结点,高度$h - 1$上的2个结点,高度$h - 2$上的$2^2$个结点以及一般在高度$h - i$上的$2^i$个结点组成。则所有结点的高度和为:
两边乘以$2$得到方程:
将这两个方程相减得到方程$(6.3)$。我们发现,非常数项几乎都消去了,例如我们有$2h - 2(h - 1) = 2$,
定理得证。
根据堆序性质,最小元总可以在根处找到。因此,我们以常数时间得到附加操作findMin。
两棵完全树(只有左边的树是堆)
尝试插入14:创建一个空穴,再将空穴上冒
将14插入到前面的堆中的最后两步
在根处建立空穴
在deleteMin中的接下来的两步
在deleteMin中的最后两步
delteMin()来完成。当一个进程由用户中止(而不是正常终止)时,必须将其从优先队列中除去。
通过项的原始集合来构造,这个构造函数将$N$项作为输入并把它们放入一个堆中。
#include <vector>
#include <exception>
template <typename Comparable>
class BinaryHeap
{
public:
explicit BinaryHeap(int capacity = 100);
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;
const Comparable& findMin() const;
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;
}
void deleteMin() {
if (isEmpty())
throw UnderflowException();
array[1] = array[currentSize--];
percolateDown(1);
}
void deleteMin(Comparable& minItem) {
if (isEmpty())
throw UnderflowException();
minItem = array[1];
array[1] = array[currentSize--];
percolateDown(1);
}
void makeEmpty();
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 = 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] = array[child];
else
break;
}
array[hole] = tmp;
}
};d堆是二叉堆的简单推广,它与二叉堆很像,但其所有的结点都有d个儿子(二叉堆是2堆)。
一个3堆(d == 3)
左式堆(leftist heap)像二叉堆那样既有结构性质,又有堆序性质;和所有使用的堆一样,左式堆具有相同的堆序性质,左式堆也是二叉树。左式堆和二叉堆唯一的区别是:左式堆不是理想平衡的(perfectly balanced),而且事实上是趋于非常不平衡的。
把任一结点$X$的零路径长(null path length)
两棵树的零路径长;只有左边的树是左式的
左式堆性质:对于堆中的每一个结点$X$,左儿子的零路径长至少与右儿子的零路径长一样大。
定理6.2 在右路径上有$r$个结点的左式树必然至少有$2^r - 1$个结点。
证明 数学归纳法证明。如果$r = 1$,则必然至少存在一个树结点。其次,设定理对$1, 2, ..., r$个结点成立。考虑在右路径上有$r + 1$个结点的左式树。此时,根具有在右路径上含有$r$个结点的右子树,以及在右路径上至少含有$r$个结点的左子树(否则它就不是左式树)。对这两条子树应用归纳假设,得知在每棵子树上最少有$2^r - 1$个结点,再加上根结点,于是在该树上至少有$2^{r+1} - 1$个结点,定理得证。
从上述定理可以得到:$N$个结点的左式树有一条右路径最多含有$\lfloor log(N + 1) \rfloor$个结点。
两个左式堆$H_1$和$H_2$
将H_2与H_1的右子堆合并的结果
将前面图中的左式堆作为$H_1$的右儿子接上后的结果
交换$H_1$的根的儿子得到的结果
合并$H_1$和$H_2$的右路径的结果
template <typename Comparable>
class LeftistHeap
{
public:
LeftistHeap();
LeftistHeap(const LeftistHeap& rhs);
~LeftistHeap();
bool isEmpty() const;
const Comparable& findMin() const;
void insert(const Comparable& x) { root = merge(new LeftistNode(x), root); };
void deleteMin() {
if (isEmpty())
throw UnderflowException();
LeftistNode *oldRoot = root;
root = merge(root->left, root->right);
delete oldRoot;
};
void deleteMin(Comparable& minItem) {
minItem = findMin(); deleteMin();
};
void makeEmpty();
void merge(LeftistHeap& rhs) {
if (this == &rhs)
return;
root = merge(root, rhs.root);
rhs.root = NULL;
};
const LeftistHeap& operator=(const LeftistHeap& rhs);
private:
struct LeftistNode
{
Comparable element;
LeftistNode *left;
LeftistNode *right;
int npl;
LeftistNode(const Comparable& theElement, LeftistNode *lt = NULL,
LeftistNode *rt = NULL, int np = 0)
: element(theElement), left(lt), right(rt), npl(np) {}
};
LeftistNode *root;
LeftistNode *merge(LeftistNode *h1, LeftistNode *h2) {
if (h1 == NULL)
return h2;
if (h2 == NULL)
return h1;
if (h1->element < h2->element)
return merge1(h1, h2);
else
return merge1(h2, h1);
};
LeftistNode *merge1(LeftistNode *h1, LeftistNode *h2) {
if (h1->left == NULL)
h1->left = h2;
else {
h1->right = merge(h1->right, h2);
if (h1->left->npl < h1->right->npl)
swapChildren(h1);
h1->npl = h1->right->npl + 1;
}
return h1;
};
void swapChildren(LeftistNode *t);
void reclaimMemory(LeftistNode *t);
LeftistNode *clone(LeftistNode *t) const;
};斜堆(skew heap)是具有堆序的二叉树,但是不存在对树的结构限制。
两个斜堆$H_1$和$H_2$
将$H_2$与$H_1$的右子堆合并的结果
合并斜堆$H_1$和$H_2$的结果
二项队列(binomial queue)不是一棵堆序的树,而是堆序的树的集合,称为森林(forest)。
二项树$B_k$由一个带有儿子$B_0, B_1, ..., B_{k - 1}$的根组成。高度为$k$的二项树恰好有$2^k$个结点,而在深度$d$处的结点数是二项系数${k \choose d}$。如果我们把堆序施加到二项树上并允许任意高度上最多一棵二项树,那么我们能够用二项树的集合唯一地表示任意大小的优先队列。
二项树$B_0, B_1, B_2, B_3以及B_4$
最小元可以通过搜索所有树的根来找出,由于最多有$logN$棵不同的树,因此最小元可以以$O(logN)$时间找到。
两个二项队列$H_1$和$H_2$
二项队列$H_3$:合并$H_1$和$H_2$的结果
依次插入1~7来构成一个二项队列
deleteMin应用到$H_3$的结果
template <typename Comparable>
class BinomialQueue
{
public:
BinomialQueue();
BinomialQueue(const Comparable& item);
BinomialQueue(const BinomialQueue& rhs);
~BinomialQueue();
bool isEmpty() const;
const Comparable& findMin() const;
void insert(const Comparable& x);
void deleteMin();
void deleteMin(Comparable& minItem) {
if (isEmpty())
throw UnderflowException();
int minIndex = findMinIndex();
minItem = theTrees[minIndex]->element;
BinomialNode* oldRoot = theTrees[minIndex];
BinomialNode* deletedTree = oldRoot->leftChild;
delete oldRoot;
// Construct H''
BinomialQueue deletedQueue;
deletedQueue.theTrees.resize(minIndex + 1);
deletedQueue.currentSize = (1 << minIndex) - 1;
for (int j = minIndex - 1; j >= 0; j--) {
deletedQueue.theTrees[j] = deletedTree;
deletedTree = deletedTree->nextsibling;
deletedQueue.theTrees[j]->nextSibling = NULL;
}
// Construct H'
theTrees[minIndex] = NULL;
currentSize -= deletedQueue.currentSize + 1;
merge(deletedQueue);
};
void makeEmpty();
void merge(BinomialQueue& rhs) {
if (this == &rhs)
return;
currentSize += rhs.currentSize;
if (currentSize > capacity()) {
int oldNumTrees = theTrees.size();
int newNumTrees = max(theTrees.size(), rhs.theTrees.size()) + 1;
theTrees.resize(newNumTrees);
for (int i = oldNumTrees; i < newNumTrees; i++)
theTrees[i] = NULL;
}
BinomialNode* carry = NULL:
for (int i = 0, j = 1; j < = currentSize; i++, j *= 2) {
BinomialNode* t1 = theTrees[i];
BinomialNode* t2 = i < rhs.theTrees.size() ? rhs.theTrees[i] : NULL;
int whichCase = t1 == NULL ? 0 : 1;
whichCase += t2 == NULL ? 0 : 2;
whichCase += carry = NULL ? 0 : 4;
switch (whichCase)
{
case 0:
case 1:
break;
case 2:
theTrees[i] = t2;
rhs.theTrees[i] = NULL;
break;
case 4:
theTrees[i] = carry;
carry = NULL;
break;
case 3:
carry = combineTrees(t1, t2);
theTrees[i] = rhs.theTrees[i] = NULL;
break;
case 5:
carry = combineTrees(t1, carry);
theTrees[i] = NULL;
break;
case 6:
carry = combineTrees(t2, carry);
rhs.theTrees[i] = NULL;
break;
case 7:
theTrees[i] = carry;
carry = combineTrees(t1, t2);
rhs.theTrees[i] = NULL;
break;
}
}
for (int k = 0; k < rhs.theTrees.size(); k++)
rhs.theTrees[k] = NULL;
rhs.currentSize = 0;
};
const BinomialQueue& operator=(const BinomialQueue& rhs);
private:
struct BinomialNode
{
Comparable element;
BinomialNode *leftChild;
BinomialNode *nextSibling;
BinomialNode(const Comparable& theElement, BinomialNode *lt, BinomialNode *rt)
: element(theElement), leftChild(lt), nextSibling(rt) {}
};
enum { DEFAULT_TREES = 1 };
int currentSize;
std::vector<BinomialNode*> theTrees;
int findMinIndex() const {
int i;
int minIndex;
for (i = 0; theTrees[i] == NULL; i++) {}
for (minIndex = i; i < theTrees.size(); i++)
if (theTrees[i] != NULL &&
theTrees[i]->element < theTrees[minIndex]->element)
minIndex = i;
return minIndex;
};
int capacity() const;
BinomialNode* combineTrees(BinomialNode *t1, BinomialNode *t2) {
if (t2->element < t1->element)
return combineTrees(t2, t1);
t2->nextSibling = t1->leftChild;
t1->leftChild = t2;
return t1;
};
void makeEmpty(BinomialNode * & t);
BinomialNode* clone(BinomialNode* t) const;
};





















