C++(数据结构与算法):42---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))
本文代码下载:
- 方式1:公众号【多栖技术控小董】回复【3586】免费获取下载链接
- 方式2:CSDN下载链接:https://download.csdn.net/download/qq_41453285/12046050
- 方式3:Github下载链接(进入之后下载里面的maxHblt.zip文件):https://github.com/dongyusheng/Interview-algorithm/tree/master/c%2B%2BAlgorithms
- 前面文章介绍了用堆实现优先级队列:https://blog.csdn.net/qq_41453285/article/details/103639243
- 用堆实现优先级队列的空间利用率很高,而且效率也很高,但是并不适用于所有优先级队列,尤其当两个优先级队列或多个长度不同的队列需要合并的时候,这时就需要其他的数据结构了,左高树就能满足这种需要
- 堆与左高树的异同:
- WBLT、HBLT可以执行优先级队列的查找、插入、删除操作,时间复杂度与堆相同
- WBLT、HBLT和堆一样,可以在线性时间内完成初始化
- 用WBLT、HBLT表示的两个优先级队列可以在对数时间内合并为一个,而桶堆表示的优先级队列却不行
一、扩充二叉树
- 如果一棵二叉树,有一类特殊的节点叫做外部节点,它替代树中的空子树。其余节点叫做内部节点
- 增加了外部节点的二叉树称为扩充二叉树
- 例如下图a是一棵二叉树,下图b是图a的扩充二叉树(外部节点用[a-f]表示)
高度s的定义
- 令s(x)是节点x到其子树的外部节点的所有路径中最短的一条,则有:
- 若x是外部节点,则s(x)=0
- 若x是内部节点,则s(x)=min{s(L),s(R)+1},其中L、R分别代码左、右孩子
- 例如,下面的扩充二叉树(图b),每个节点的s值如图c所示
重量w的定义
- 如果不以路径长度s来对左高树进行定义,而是以节点数目来定义,那么就可以得到另一种左高树
- 节点的重量(w):
- 若x是外部节点,则w(x)=0
- 若x是内部节点,则w(x)=w(子节点的重量)+1
- 例如,下面的扩充二叉树,每个节点的w值如下图所示
二、高度优先左高树(HBLT)
- 定义:当且仅当其任何一个内部节点的左孩子的s值都大于或等于右孩子的s值,称为高度优先左高树(height-biased leftist tree,HBLT)
- 例如下图就是一个高度优先左高树(HBLT),其中阴影部分代表外部节点
定理
- 令x为HBLT的一个内部节点,则有:
- 1).以x为根的子树的节点数目至少为
- 2).若以x为根的子树有m个节点,那么s(x)最多为
- 3).从x到一外部节点的最右路径(即从x开始沿右孩子移动的路径)的长度为s(x)
最大HBLT、最小HBLT
- 若一棵HBLT同是还是大根树,那么称之为最大HBLT(max HBLT),可用于实现最大优先级队列
- 若一棵HBLT同是还是小根树,那么称之为最小HBLT(min HBLT),可用于实现最小优先级队列
- 例如下图3个都是最大HBLT
- 例如下图3个都是最小HBLT
最大HBLT的插入
- 最大HBLT的插入操作可利用最大HBLT的合并操作来实现
- 插入的策略:
- 1.假设元素x要插入名为H的最大HBLT中
- 2.先建立一棵新的只包含x元素的HBLT
- 3.然后将这棵新的HBLT与名为H的HBLT进行合并
- 4.合并之后的HBLT就为插入之后最终的HBLT
最大HBLT的删除
- 最大HBLT与大根堆一样,最大元素在根中。因此删除操作也是删除根节点
- 删除操作也是利用最大HBLT的合并操作来实现
- 删除的策略:
- 1.若根被删除,则分别以根节点的左右孩子节点为根的子树是两棵最大HBLT
- 2.然后将这棵最大HBLT合并,之后便是删除后的结果
两棵HBLT的合并
- 具有n个元素的HBLT,其最右路径的长度为O()。一个算法要合并两棵HBLT,只能在遍历其最右路径中进行。由于在每个节点上实施合并所需时间为O(1),所以合并算法的时间复杂性是合并后节点数的对数。因此算法从两棵HBLT的根开始仅沿右孩子移动
- 合并的策略:
- 1.假设A、B是两棵需要合并的最大HBLT。如果一者为空,则另一个便是合并的结果;如果两者均不为空,则进行以下步骤
- 2.先比较两个HBLT的根,较大者的根作为合并后的HBLT的根
- 3.假设A的根大于B,且A的左子树为L
- 4.然后将A的右子树与B合并,然后形成一棵名为C的HBLT(如果A没有右子树,那B就什么都不做)
- 5.然后将C与A合并
- 6.如果L的s值小于C的s值,则以C为左子树,L为右子树;否则,L为左子树,C为右子树
- 合并的策略需要使用递归来实现(因为通过上面的步骤我们知道,如果将A的右子树与B进行合并,也是一次HBLT合并的操作,因此是递归的特性)
- 下面的演示案例中:每个节点的s值标在节点的外部,元素的值标在节点的里面。并且根较大的树我们放置在左边,右边的HBLT用阴影表示
- 演示案例1:
- 1.假设a)是两棵想要合并的HBLT
- 2.因为9的右子树为空,所以就不存在9的右子树与7进行合并的操作
- 3.然后将7与9为根的HBLT进行合并(图b)所示)
- 4.合并之后9的左子树的s值为0(也就是外部节点的s值),而右子树(7)的s值为1,因此将7作为9的左子树。最终合并的结果如c)所示
- 演示案例2:
- 1.假设d)是两棵想要合并的HBLT
- 2.因为10的右子树为空,所以就不存在10的右子树与7进行合并的操作
- 3.然后将7与9为根的HBLT合并(如图e)所示)
- 4.合并之后检查10的左子树的s值与右子树的s值相等,因此就不交换。最终的结果如图e)所示
- 演示案例3:
- 1.假设f)是两棵想要合并的HBLT
- 2.18的右子树不为空,因此需要将18的右子树与10为根的HBLT进行合并,合并之后的结果如上图e)所示
- 3.18的右子树与10为根的HBLT合并之后产生一个新的HBLT,然后将这个HBLT作为18的右子树(如图g)所示)
- 4.合并之后,然后开始比较18的左右子树的s值,比较之后,发现18的右子树的s值比左子树的s值大,因此需要将左右子树进行互换。互换之后的结果如h)所示,这也是最终的合并结果
- 演示案例4:
- 1.假设i)是两棵想要合并的HBLT
- 2.因为40的右子树不为空,因此将40的右子树与以18为根的HBLT进行合并,合并的结果就是我们上面所介绍的h)所示
- 3.合并之后将h)作为20的右子树(如图j)所示)
- 4.合并之后,然后开始比较40的左右子树的s值,比较之后,发现40的右子树的s值比左子树的s值大,因此需要将左右子树进行互换。互换之后的结果如k)所示,这也是最终的合并结果
最大HBLT的初始化
- 初始化过程是将n个元素逐个插入最初为空的最大HBLT,所需时间为O()。并且也用到了合并操作
- 初始化的策略:
- 1.首先创建n个仅包含一个元素的最大HBLT,将这n棵树组成一个FIFO队列
- 2.然后从队列中依次成对删除HBLT,然后将其合并后再插入队列末尾,直到队列只有一棵HBLT位置
- 演示案例:
- 1.假设我们希望构造一棵最大HBLT,其具有5个元素:7、1、9、11、2
- 2.先将这5个元素做个分别作为5棵单独的HBLT,然后组成一个FIFO队列
- 3.接着从队列中取出一对HBLT进行合并(上面可以看出是7和1这两个HBLT),将7和1进行合并(如图a)所示)
- 4.然后将7和1组成的HBLT放入到队列的尾部
- 5.接着从队列头部再次取出两棵HBLT,此时为9和11,然后将9和11组成一棵HBLT(如图b)所示)
- 6.然后将9和11组成的HBLT放入到队列的尾部(注意,此时队列中只有3棵HBLT了)
- 7.然后从队列中再取出两棵HBLT进行合并,此时取出的是1和7、2组成的HBLT,然后将两者合并(如图c所示)
- 8.然后将合并后的结果放入到队列尾部(注意,此时队列中只有2棵HBLT了)
- 9.最后队列中只有两棵HBLT了(就是c)和b)),然后将b)和c)进行合并,最终的结果如d)所示
二、重量优先左高树(WBLT)
- 定义:当且仅当其任何一个内部节点的左孩子的w值都大于或等于其右孩子的w值,称为重量优先左高树(wieght-biased leftist tree,WBLT)
- 最大HBLT、最小HBLT:
- 若一棵WBLT同是还是大根树,那么称之为最大WBLT(max WBLT),可用于实现最大优先级队列
- 若一棵WBLT同是还是小根树,那么称之为最小WBLT(min WBLT),可用于实现最小优先级队列
- 与HBLT异同:
- 同HBLT类似,具有m个节点的WBLT的最右路径长度最多为
- WBLT的查找、插入、删除、合并、初始化和HBLT的类似,所以可以参考上面HBLT的操作
三、编码实现(最大HBLT)
头文件定义
#include <iostream> #include <string> #include <queue>using std::cout; using std::endl; using std::cin; using std::string; using std::pair; using std::queue;
异常类定义
class illegalParameterValue {string message; public:illegalParameterValue(const char *theMessage = "Illegal Parameter Value") :message(theMessage) {}const char *what() {return message.c_str();} };class queueEmpty { public:queueEmpty(string theMessage = "Invalid operation on empty queue"){message = theMessage;}const char *what() {return message.c_str();} private:string message; };
树节点定义
- 用来表示树中的每一个节点
template<typename T> struct binaryTreeNode {T element;struct binaryTreeNode<T>* leftChild, *rightChild;binaryTreeNode() { this->leftChild = this->rightChild = nullptr; }binaryTreeNode(const T& theElement) :element(theElement) {this->leftChild = this->rightChild = nullptr;}binaryTreeNode(const T& theElement,const struct binaryTreeNode<T>* theLeftChild,const struct binaryTreeNode<T>* theRightChild) :element(theElement) {this->leftChild = theLeftChild;this->rightChild = theRightChild;} };
抽象类定义
template<typename T> class maxPriorityQueue { public:virtual ~maxPriorityQueue() {}virtual bool empty()const = 0;//当队列为空返回true;否则返回falsevirtual int size()const = 0;//返回队列的元素个数virtual const T& top() = 0;//返回优先级最大的元素的引用virtual void pop() = 0; //删除队首元素virtual void push(const T& theElement) = 0;//插入元素theElement };
主类定义
- 主类继承于maxPriorityQueue抽象类
- 其中每个树节点类型为std::pait<int,T>类型
- int:代表每个节点的s值
- T:代表每个节点的数值
template<typename T> class maxHblt :public maxPriorityQueue<T> { public:maxHblt() { this->root = nullptr; this->treeSize = 0;}~maxHblt() { erase(); }bool empty()const override {//当队列为空返回true;否则返回falsereturn this->treeSize == 0;}int size()const override { //返回队列的元素个数return this->treeSize;}const T& top()override { //返回优先级最大的元素的引用if (this->treeSize == 0)throw queueEmpty();return this->root->element.second;}void pop()override; //删除队首元素void push(const T& theElement)override;//插入元素theElementvoid initialize(T* theElement, int theSize);//初始化一个HBLTvoid erase(); //清空树void meld(maxHblt<T>& theHblt); //将本棵HBLT与参数所指的HBLT进行合并,内部调用私有方法meldvoid preOrderOutput()const { preOrder(this->root); }void inOrderOutput()const { inOrder(this->root); }void postOrderOutput()const { postOrder(this->root); }private:void preOrder(binaryTreeNode<std::pair<int, T>>* x)const;void inOrder(binaryTreeNode<std::pair<int, T>>* x)const;void postOrder(binaryTreeNode<std::pair<int, T>>* x)const;void clearTree(binaryTreeNode<std::pair<int, T>>* t);void meld(binaryTreeNode<std::pair<int,T>>* &x, binaryTreeNode<std::pair<int,T>>*& y); private:binaryTreeNode<std::pair<int,T>>* root; //根节点int treeSize; //树的大小 };
meld函数
- 根据上面介绍的HBLT合并原理,定义了此函数,用来合并两棵HBLT树
//将x和y两棵HBLT合并 template<typename T> void maxHblt<T>::meld(binaryTreeNode<std::pair<int, T>>* &x, binaryTreeNode<std::pair<int, T>>*& y) {//如果y为空,不进行合并if (y == nullptr)return;//如果x为空,那么就将y赋值给xif (x == nullptr){x = y;return;}//如果x的值小于y的值,进行交换if (x->element.second < y->element.second)std::swap(x, y);//将x的右子节点与y合并。如果x没有右子节点,那么就将y设置为x的右子节点meld(x->rightChild, y);//如果x的左子节点为空,将右子节点设置为左子节点if (x->leftChild == nullptr) {x->leftChild = x->rightChild;x->rightChild = nullptr;//因为把右子节点赋值给左子节点了,所以右子节点为空了,那么本节点的s值就为1了x->element.first = 1;}else //如果左子节点不为空,比较是否需要交换{//如果左子节点的s值比右子节点的小,那么就进行交换if (x->leftChild->element.first < x->rightChild->element.first) {std::swap(x->leftChild, x->rightChild);}//因为右子节点到外部节点之间的s值是最小的,所以就将x的s值设置为右子节点的s值+1x->element.first = x->rightChild->element.first + 1;} }
pop、push函数定义
- 根据上面最大HBLT的删除、原理定义了下面的函数,都调用了meld合并函数
template<typename T> void maxHblt<T>::pop() {//如果HBLT为空,不能出队列if (this->treeSize == 0)throw queueEmpty();//得到根节点的左右子节点binaryTreeNode<std::pair<int, T>>* left = this->root->leftChild,*right=this->root->rightChild;//删除根节点,将左子节点变为新根节点,然后进行合并delete this->root;this->root = left;meld(this->root, right);this->treeSize--; }template<typename T> void maxHblt<T>::push(const T& theElement) {//创建一个新节点binaryTreeNode<std::pair<int, T>> *q = new binaryTreeNode<std::pair<int, T>>(std::pair<int,T>(1, theElement));//将新节点与本HBLT进行合并this->meld(this->root,q);this->treeSize++; }
erase、clearTree函数
- erase用来清空整个HBLT树,调用了clearTree函数
template<typename T> void maxHblt<T>::erase() {//调用clearTreeclearTree(this->root);this->root = nullptr;this->treeSize = 0; }template<typename T> void maxHblt<T>::clearTree(binaryTreeNode<std::pair<int, T>>* t) {//后序遍历删除if (t){clearTree(t->leftChild);clearTree(t->rightChild);delete t;} }
initialize函数
- 根据上面介绍的FIFO初始化HBLT原则,定义了此函数
template<typename T> void maxHblt<T>::initialize(T* theElement, int theSize) {//创建一个队列,用来初始化HBLTstd::queue<binaryTreeNode<std::pair<int, T>>*> q;//清空当前HBLTerase();//先建立一组HBLT,每个HBLT中只有一个节点for (int i = 1; i <=theSize; ++i)q.push(new binaryTreeNode<std::pair<int, T>>(std::pair<int, T>(1, theElement[i])));//theSize个HBLT,需要合并theSize-1次for (int i = 1; i <= theSize - 1; ++i){//从队列中取出两个HBLT进行合并binaryTreeNode<std::pair<int, T>>* b = q.front();q.pop();binaryTreeNode<std::pair<int, T>>* c = q.front();q.pop();//合并meld(b, c);//合并之后再次放入到队列中q.push(b);}if (theSize > 0)this->root = q.front();this->treeSize = theSize; }
打印函数
- 分别为前序遍历、中序遍历、后序遍历
template<typename T> void maxHblt<T>::preOrder(binaryTreeNode<std::pair<int, T>>* x)const {if (x){std::cout << x->element.second<<" ";preOrder(x->leftChild);preOrder(x->rightChild);} }template<typename T> void maxHblt<T>::inOrder(binaryTreeNode<std::pair<int, T>>* x)const {if (x){inOrder(x->leftChild);std::cout << x->element.second << " ";inOrder(x->rightChild);} }template<typename T> void maxHblt<T>::postOrder(binaryTreeNode<std::pair<int, T>>* x)const {if (x){postOrder(x->leftChild);postOrder(x->rightChild);std::cout << x->element.second << " ";} }
复杂度分析
演示案例1
int main() {maxHblt<int>* myMaxHblt = new maxHblt<int>();myMaxHblt->push(7);myMaxHblt->push(5);myMaxHblt->push(10);myMaxHblt->push(3);myMaxHblt->preOrderOutput();std::cout << std::endl;myMaxHblt->inOrderOutput();std::cout << std::endl;myMaxHblt->postOrderOutput();std::cout << std::endl;std::cout << std::endl;myMaxHblt->pop();myMaxHblt->preOrderOutput();std::cout << std::endl;myMaxHblt->inOrderOutput();std::cout << std::endl;myMaxHblt->postOrderOutput();std::cout << std::endl;std::cout << std::endl;return 0; }
- 演示结果如下图所示:
- 根据插入原则,其步骤如下
- 当pop一个元素之后(删除根节点10),其步骤如下
演示案例2
int main() {maxHblt<int>* myMaxHblt = new maxHblt<int>();int arr[5] = { 0,7,5,10,3 };myMaxHblt->initialize(arr,4);myMaxHblt->preOrderOutput();std::cout << std::endl;myMaxHblt->inOrderOutput();std::cout << std::endl;myMaxHblt->postOrderOutput();std::cout << std::endl;std::cout << std::endl;myMaxHblt->pop();myMaxHblt->preOrderOutput();std::cout << std::endl;myMaxHblt->inOrderOutput();std::cout << std::endl;myMaxHblt->postOrderOutput();std::cout << std::endl;std::cout << std::endl;return 0; }
- 这个演示案例调用了intialize函数,元素与演示案例1都是一样的,结果应该与演示案例1一样(备注:但是不知道为什么,结果与演示案例1不一样,待更新)
C++(数据结构与算法):42---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))相关推荐
- 数据结构与算法之循环队列的操作
数据结构与算法之循环队列的操作 /* 循环队列的入队和出队算法设计 初始化循环队列 .打印队列.插入元素到循环队列.获取循环队列的首元素,元素不出队.出队.获取循环队列元素个数.判断循环队列的空和满. ...
- python 熊猫钓鱼_Python数据结构与算法之使用队列解决小猫钓鱼问题
本文实例讲述了Python数据结构与算法之使用队列解决小猫钓鱼问题.分享给大家供大家参考,具体如下: 按照<啊哈>里的思路实现这道题目,但是和结果不一样,我自己用一幅牌试了一下,发现是我的 ...
- 数据结构与算法-栈与队列
数据结构与算法-栈与队列 栈 基本概念 简单表述就是仅在表尾进行插入和删除操作的线性表. 常见操作 入栈和出栈, 均在线性表的尾部进行. 基本原则就是, 先入后出. 队列 基本概念 和栈不同的是,队列 ...
- 【数据结构Python描述】优先级队列描述“银行VIP客户插队办理业务”及“被插队客户愤而离去”的模型实现
文章目录 一.支持插队模型的优先级队列 队列ADT扩充 队列记录描述 方法理论步骤 `update(item, key, value)` `remove(item)` 二.支持插队模型的优先级队列实现 ...
- char栈java,Java数据结构与算法-栈和队列(示例代码)
(摘录加总结)------ 栈和队列不属于基础的数据结构,它们都属于线性表. 一.栈 对于栈存储操作元素只能在栈结构的一端进行元素的插入和删除,是一种性质上的线性表结构.按照"先进后出&qu ...
- 2.2基本算法之递归和自调用函数_数据结构与算法之5——队列和栈
栈和队列比较简单,而且实用性非常广泛,这里主要介绍一下他们的概念和实现,在很多算法中,栈和队列的运用很重要,因此,虽然简单确是最重要的数据结构之一,必须重视. 栈是保证元素后进先出(后存入者先使用,L ...
- java判断栈中元素数目_Java数据结构与算法-栈和队列
(摘录加总结)------ 栈和队列不属于基础的数据结构,它们都属于线性表. 一.栈 对于栈存储操作元素只能在栈结构的一端进行元素的插入和删除,是一种性质上的线性表结构.按照"先进后出&qu ...
- 【数据结构与算法图文动画详解】终于可以彻底弄懂:红黑树、B-树、B+树、B*树、满二叉树、完全二叉树、平衡二叉树、二叉搜索树...
1.树简介 1.1基本概念 树是由结点或顶点和边组成的(可能是非线性的)且不存在着任何环的一种数据结构.没有结点的树称为空(null或empty)树.一棵非空的树包括一个根结点,还(很可能)有多个附加 ...
- 数据结构和算法——栈、队列、堆
文章目录 1.预备知识 1.1 栈 1.2 队列 1.3 堆 2.用队列实现栈 2.1 题目描述 2.2 解题思路 2.3 C++实现 3.用栈实现队列 3.1 题目描述 3.2 解题思路 3.3 C ...
- 经典数据结构和算法 双端队列 java
选一个简单的数据结构聊一聊,话说有很多数据结构都在玩组合拳,比如说:块状链表,块状数组,当然还有本篇的双端队列,是的,它就是 栈和队列的组合体. 一:概念 我们知道普通队列是限制级的一端进,另一端出的 ...
最新文章
- Python:matplotlib实践:直方图、散点图展示、变色、线条变换、点样式变换、添加名称、设置横纵轴范围、在一张图上显示多条线
- .net framework 2.0 安装包下载url
- 基于matlab fdma传输系统设计,基于MATLAB的LTE系统仿真研究
- 各国自动驾驶政策概况及特征
- Nginx配置proxy_pass转发的/路径问题
- ML 01、机器学习概论
- [密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第39篇]侧信道攻击和故障攻击有什么区别
- mysql rac_MySQL Galera集群和ORACLE RAC的实现原理比较
- System.Windows.Forms.Application.DoEvents();
- 实习成长之路:后端开发实践系列——领域驱动设计(DDD)编码实践一
- wait放弃对象锁_Java线程:notify()和wait()示例
- kali免杀工具shellter
- 猪齿鱼开源四周年庆典,邀您共享精彩瞬间
- 操作系统 | 银行家算法
- openwrt路由器打印机服务器设置_openwrt路由器打印机服务器设置_TP-Link无线路由器打印机设置指南...
- excel设置斑马线
- 两个Listlt;Mapgt;快速合并
- git command
- 数据分析指标体系搭建实战!
- python中国最好大学排名_国内大学排名如何?用Python爬取中国大学排名
热门文章
- CP-ABE方案形式化定义及安全模型总结
- Web安全防范-----防止重放攻击
- ESP8266+micropython+HCSR04 超声波测距传感器
- conceptdraw office(专业办公套件) v6.0.0附安装教程
- 加js库和css库| jQuery hover()用法 |document.onreadystatechange |get和post
- JEECG3.3.0 配置大鱼短信
- wallpaper engine怎么用?
- 基于AKA的IMS接入认证机制
- java aes ctr_AES CBC和CTR加解密实例
- 用深度学习每次得到的结果都不一样,怎么办?