【优先队列(堆)】二项队列类模板的实现
左式堆和斜堆都在每次操作以O(logN)时间有效地支持合并、插入和deleteMin,但还有改进的余地,二叉堆以每次操作平均花费常数时间支持插入。二项队列支持所有这三种操作,每次操作的最坏情形运行时间为O(logN),但插入操作平均花费常数时间。
二项队列构建
一个二项队列(binomial queue)不是一棵堆序的树,而是一组堆序树,称为森林(forest)。堆序树中的每一棵都是有约束的形式,叫作二项树(binomial tree)。每一个高度上至多存在一棵二项树。高度为0的二项树是一棵单节点树,高度为k的二项树Bk通过将一棵二项树Bk-1附接到另一棵二项树Bk-1的根上而构成。
从图中可以看到,二项树Bk由一个带有儿子B0,B1,……,Bk-1的根组成。高度为k的二项树恰好有2k个节点,而在深度d处的节点个数是二项系数。如果把堆序施加到二项树上并允许任意高度上最多一棵二项树,那么我们就能够用二项树的集合表示任意大小的优先队列。
二项队列操作
最小元可以通过搜索所有树的根来找出,由于最多有logN棵不同的树,因此最小元可以时间O(logN)找到。
两个二项队列的合并
有两个二项队列H1和H2,分别具有6个和7个元素,见图2。
合并操作通过将两个队列加到一起来完成,令H3是新的二项队列,将H2中高度为0的二项树添加到H3中,再将两个高度为1的二项树相加,让大的根成为小的根的子树,从而建立高度为2的二项树,如图3所示。
然后将高度为2的两棵二项树合并,得到高度为3的二项树,由于H1和H2都没有高度为3的二项树,因此该二项树就成为H3的一部分,合并结束。最后得到的二项队列如图4所示。
插入
插入实际上是特殊情况的合并,即,只要创建一棵单节点树并执行一次合并即可,这种操作最坏情形运行时间也是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; } };
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!