竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生

【优先队列(堆)】二项队列类模板的实现

左式堆和斜堆都在每次操作以O(logN)时间有效地支持合并、插入和deleteMin,但还有改进的余地,二叉堆以每次操作平均花费常数时间支持插入。二项队列支持所有这三种操作,每次操作的最坏情形运行时间为O(logN),但插入操作平均花费常数时间。

二项队列构建

一个二项队列(binomial queue)不是一棵堆序的树,而是一组堆序树,称为森林(forest)。堆序树中的每一棵都是有约束的形式,叫作二项树(binomial tree)。每一个高度上至多存在一棵二项树。高度为0的二项树是一棵单节点树,高度为k的二项树Bk通过将一棵二项树Bk-1附接到另一棵二项树Bk-1的根上而构成。
在这里插入图片描述

图1 二项树B0、B1、B2、B3和B4

从图中可以看到,二项树Bk由一个带有儿子B0,B1,……,Bk-1的根组成。高度为k的二项树恰好有2k个节点,而在深度d处的节点个数是二项系数。如果把堆序施加到二项树上并允许任意高度上最多一棵二项树,那么我们就能够用二项树的集合表示任意大小的优先队列。

二项队列操作

最小元可以通过搜索所有树的根来找出,由于最多有logN棵不同的树,因此最小元可以时间O(logN)找到。

两个二项队列的合并

有两个二项队列H1和H2,分别具有6个和7个元素,见图2。
在这里插入图片描述

图2 两个二项队列H1和H2

合并操作通过将两个队列加到一起来完成,令H3是新的二项队列,将H2中高度为0的二项树添加到H3中,再将两个高度为1的二项树相加,让大的根成为小的根的子树,从而建立高度为2的二项树,如图3所示。
在这里插入图片描述

图3 H1和H2中两棵B1树的合并

然后将高度为2的两棵二项树合并,得到高度为3的二项树,由于H1和H2都没有高度为3的二项树,因此该二项树就成为H3的一部分,合并结束。最后得到的二项队列如图4所示。
在这里插入图片描述

图4 二项队列H3:合并H1和H2的结果

插入

插入实际上是特殊情况的合并,即,只要创建一棵单节点树并执行一次合并即可,这种操作最坏情形运行时间也是O(logN)

分析指出,对一个初始为空的二项队列进行N次insert将花费O(N)最坏情形时间。

实现代码

注意:二项树的每一个节点都将包含数据、第一个儿子以及右兄弟。二项树中的诸儿子以递减次序排列。

class BinomialQueue {
private:
struct BinomialNode {
Comparable element;
BinomialNode* leftChild;
BinomialNode* nextSibling;
BinomialNode(const Comparable&e,BinomialNode*lt,BinomialNode*rt)
:element{e},leftChild{lt},nextSibling(rt){}
BinomialNode(Comparable&&e,BinomialNode*lt,BinomialNode*rt)
:element{std::move(e)},leftChild{lt},nextSibling{rt}{}
};
const static int DEFAULT_TREES = 1;
std::vector<BinomialNode*> theTrees; //树根组成的数组
int currentSize; //优先队列中的项数
/**
* 寻找优先队列中包含最小项的树的下标
* 队列不为空
* 返回包含最小项的树的下标
*/
int findMinIndex()const
{
int i;
int minIndex;
for (i = 0; theTrees[i] == nullptr; ++i)
;
for (minIndex = i; i < theTrees.size(); ++i)
if (theTrees[i] != nullptr &&
theTrees[i]->element < theTrees[minIndex]->element)
minIndex = i;
return minIndex;
}
int capacity()const {
return (1 << theTrees.size()) - 1;
}
/**
* 返回合并t1和t2的结果,其中t1和t2大小相同
*/
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)
{
if (t != nullptr) {
makeEmpty(t->leftChild);
makeEmpty(t->nextSibling);
delete t;
t = nullptr;
}
}
BinomialNode* clone(BinomialNode* t)const {
if (t == nullptr)
return nullptr;
else
return new BinomialNode{ t->element,clone(t->leftChild),clone(t->nextSibling) };
}
public:
BinomialQueue() : theTrees(DEFAULT_TREES)
{
for (auto& root : theTrees)
root = nullptr;
currentSize = 0;
}
BinomialQueue(const Comparable& item) : theTrees(1), currentSize{ 1 }
{ theTrees[0] = new BinomialNode{ item, nullptr, nullptr }; }
BinomialQueue(const BinomialQueue& rhs)
: theTrees(rhs.theTrees.size()), currentSize{ rhs.currentSize }
{
for (int i = 0; i < rhs.theTrees.size(); ++i)
theTrees[i] = clone(rhs.theTrees[i]);
}
BinomialQueue(BinomialQueue&& rhs)
: theTrees{ std::move(rhs.theTrees) }, currentSize{ rhs.currentSize }
{
}
~BinomialQueue() { makeEmpty(); }
BinomialQueue& operator=(const BinomialQueue& rhs) {
BinomialQueue copy = rhs;
std::swap(*this, copy);
return *this;
}
BinomialQueue& operator=(BinomialQueue&& rhs) {
std::swap(currentSize, rhs.currentSize);
std::swap(theTrees, rhs.theTrees);
return *this;
}
bool isEmpty()const { return currentSize == 0; }
/**
* 找出包含优先队列中最小项的树的下标
* 这个优先队列必须不空
* 返回包含最小项的树的下标
*/
const Comparable& findMin()const
{
if (isEmpty())
throw UnderflowException{ };
return theTrees[findMinIndex()]->element;
}
/**
* 插入x项,允许重复
*/
void insert(const Comparable& x) {
BinomialQueue oneItem{ x };
merge(oneItem);
}
void insert(Comparable&& x) {
BinomialQueue oneItem{ std::move(x)};
merge(oneItem);
}
void deleteMin() {
Comparable x;
deleteMin(x);
}
/**
* 删除最小项并把它放入minItem
* 若为空,则抛出UnderflowException异常
*/
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;
//构建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 = nullptr;
}
//构建H'
theTrees[minIndex] = nullptr;
currentSize -= deletedQueue.currentSize + 1;
merge(deletedQueue);
}
void makeEmpty() {
currentSize = 0;
for (auto& root : theTrees)
makeEmpty(root);
}
/**
* 将rhs合并到优先队列中
* rhs变为空,rhs必须不同于this
*/
void merge(BinomialQueue& rhs)
{
if (this == &rhs) //避免别名问题
return;
currentSize += rhs.currentSize;
if (currentSize > capacity()) {
int oldNumTrees = theTrees.size();
int newNumTrees = std::max(theTrees.size(), rhs.theTrees.size()) + 1;
theTrees.resize(newNumTrees);
for (int i = oldNumTrees; i < newNumTrees; ++i)
theTrees[i] = nullptr;
}
BinomialNode* carry = nullptr; //carry是从上一步得来的树
for (int i = 0, j = 1; j <= currentSize; ++i, j *= 2)
{
BinomialNode* t1 = theTrees[i];
BinomialNode* t2 = i < rhs.theTrees.size() ? rhs.theTrees[i] : nullptr;
int whichCase = t1 == nullptr ? 0 : 1;
whichCase += t2 == nullptr ? 0 : 2;
whichCase += carry == nullptr ? 0 : 4;
switch (whichCase)
{
case 0: //无树的情况
case 1: //只有this的情形
break;
case 2: //只有rhs的情形
theTrees[i] = t2;
rhs.theTrees[i] = nullptr;
break;
case 4: //只有carry的情形
theTrees[i] = carry;
carry = nullptr;
break;
case 3: //this和rhs的情形
carry = combineTrees(t1, t2);
theTrees[i] = rhs.theTrees[i] = nullptr;
break;
case 5: //this和carry的情形
carry = combineTrees(t2, carry);
theTrees[i] = nullptr;
break;
case 6: //rhs和carry的情形
carry = combineTrees(t2, carry);
rhs.theTrees[i] = nullptr;
break;
case 7: //全体树的情形
theTrees[i] = carry;
carry = combineTrees(t1, t2);
rhs.theTrees[i] = nullptr;
break;
}
}
for (auto& root :rhs.theTrees)
root = nullptr;
rhs.currentSize = 0;
}
};
posted @   aw11  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示