C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))

前言

  • 本文代码下载:
    • CSDN下载链接:https://download.csdn.net/download/qq_41453285/12046050
    • Github下载链接(为其中的maxHblt.zip文件):https://github.com/dongyusheng/algorithm
  • 前面文章介绍了用堆实现优先级队列:https://blog.csdn.net/qq_41453285/article/details/103639243
  • 用堆实现优先级队列的空间利用率很高,而且效率也很高,但是并不适用于所有优先级队列,尤其当两个优先级队列或多个长度不同的队列需要合并的时候,这时就需要其他的数据结构了,左高树就能满足这种需要
  • 堆与左高树的异同:
    • WBLT、HBLT可以执行优先级队列的查找、插入、删除操作,时间复杂度与堆相同
    • WBLT、HBLT和堆一样,可以在线性时间内完成初始化
    • 用WBLT、HBLT表示的两个优先级队列可以在对数时间内合并为一个,而桶堆表示的优先级队列却不行

一、扩充二叉树

  • 如果一棵二叉树,有一类特殊的节点叫做外部节点,它替代树中的空子树。其余节点叫做内部节点
  • 增加了外部节点的二叉树称为扩充二叉树
  • 例如下图a是一棵二叉树,下图b是图a的扩充二叉树(外部节点用[a-f]表示)

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第1张图片

高度s的定义

  • 令s(x)是节点x到其子树的外部节点的所有路径中最短的一条,则有:
    • 若x是外部节点,则s(x)=0
    • 若x是内部节点,则s(x)=min{s(L),s(R)+1},其中L、R分别代码左、右孩子
  • 例如,下面的扩充二叉树(图b),每个节点的s值如图c所示

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第2张图片

重量w的定义

  • 如果不以路径长度s来对左高树进行定义,而是以节点数目来定义,那么就可以得到另一种左高树
  • 节点的重量(w):
    • 若x是外部节点,则w(x)=0
    • 若x是内部节点,则w(x)=w(子节点的重量)+1
  • 例如,下面的扩充二叉树,每个节点的w值如下图所示

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第3张图片

二、高度优先左高树(HBLT)

  • 定义:当且仅当其任何一个内部节点的左孩子的s值都大于或等于右孩子的s值,称为高度优先左高树(height-biased leftist tree,HBLT)
  • 例如下图就是一个高度优先左高树(HBLT),其中阴影部分代表外部节点

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第4张图片

定理

  • 令x为HBLT的一个内部节点,则有:
    • 1).以x为根的子树的节点数目至少为2^{s(x)}-1
    • 2).若以x为根的子树有m个节点,那么s(x)最多为
    • 3).从x到一外部节点的最右路径(即从x开始沿右孩子移动的路径)的长度为s(x)

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第5张图片

最大HBLT、最小HBLT

  • 若一棵HBLT同是还是大根树,那么称之为最大HBLT(max HBLT),可用于实现最大优先级队列
  • 若一棵HBLT同是还是小根树,那么称之为最小HBLT(min HBLT),可用于实现最小优先级队列
  • 例如下图3个都是最大HBLT

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第6张图片

  • 例如下图3个都是最小HBLT

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第7张图片

最大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(\log n)。一个算法要合并两棵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)所示

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第8张图片

  • 演示案例2:
    • 1.假设d)是两棵想要合并的HBLT
    • 2.因为10的右子树为空,所以就不存在10的右子树与7进行合并的操作
    • 3.然后将7与9为根的HBLT合并(如图e)所示)
    • 4.合并之后检查10的左子树的s值与右子树的s值相等,因此就不交换。最终的结果如图e)所示

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第9张图片

  • 演示案例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)所示,这也是最终的合并结果

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第10张图片

  • 演示案例4:
    • 1.假设i)是两棵想要合并的HBLT
    • 2.因为40的右子树不为空,因此将40的右子树与以18为根的HBLT进行合并,合并的结果就是我们上面所介绍的h)所示
    • 3.合并之后将h)作为20的右子树(如图j)所示)
    • 4.合并之后,然后开始比较40的左右子树的s值,比较之后,发现40的右子树的s值比左子树的s值大,因此需要将左右子树进行互换。互换之后的结果如k)所示,这也是最终的合并结果

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第11张图片

最大HBLT的初始化

  • 初始化过程是将n个元素逐个插入最初为空的最大HBLT,所需时间为O(n\log n)。并且也用到了合并操作
  • 初始化的策略:
    • 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)所示

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第12张图片

二、重量优先左高树(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 
#include 
#include 

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
struct binaryTreeNode
{
	T element;
	struct binaryTreeNode* 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* theLeftChild,
		const struct binaryTreeNode* theRightChild) :element(theElement) {
		this->leftChild = theLeftChild;
		this->rightChild = theRightChild;
	}
};

抽象类定义

template
class maxPriorityQueue
{
public:
	virtual ~maxPriorityQueue() {}
	virtual bool empty()const = 0;//当队列为空返回true;否则返回false
	virtual 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:代表每个节点的s值
    • T:代表每个节点的数值
template
class maxHblt :public maxPriorityQueue
{
public:
	maxHblt() { 
		this->root = nullptr; 
		this->treeSize = 0;
	}
	~maxHblt() { erase(); }

	bool empty()const override {//当队列为空返回true;否则返回false
		return 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;//插入元素theElement
	void initialize(T* theElement, int theSize);//初始化一个HBLT
	
	void erase(); //清空树
	void meld(maxHblt& theHblt); //将本棵HBLT与参数所指的HBLT进行合并,内部调用私有方法meld
	void preOrderOutput()const { preOrder(this->root); }
	void inOrderOutput()const { inOrder(this->root); }
	void postOrderOutput()const { postOrder(this->root); }
	
private:
	void preOrder(binaryTreeNode>* x)const;
	void inOrder(binaryTreeNode>* x)const;
	void postOrder(binaryTreeNode>* x)const;

	void clearTree(binaryTreeNode>* t);
	void meld(binaryTreeNode>* &x, binaryTreeNode>*& y);
private:
	binaryTreeNode>* root; //根节点
	int treeSize;            //树的大小
};

meld函数

  • 根据上面介绍的HBLT合并原理,定义了此函数,用来合并两棵HBLT树
//将x和y两棵HBLT合并
template
void maxHblt::meld(binaryTreeNode>* &x, binaryTreeNode>*& y)
{
	//如果y为空,不进行合并
	if (y == nullptr)
		return;
	//如果x为空,那么就将y赋值给x
	if (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值+1
		x->element.first = x->rightChild->element.first + 1;
	}
}

pop、push函数定义

  • 根据上面最大HBLT的删除、原理定义了下面的函数,都调用了meld合并函数
template
void maxHblt::pop()
{
	//如果HBLT为空,不能出队列
	if (this->treeSize == 0)
		throw queueEmpty();

	//得到根节点的左右子节点
	binaryTreeNode>* left = this->root->leftChild,
		*right=this->root->rightChild;

	//删除根节点,将左子节点变为新根节点,然后进行合并
	delete this->root;
	this->root = left;
	meld(this->root, right);
	this->treeSize--;
}

template
void maxHblt::push(const T& theElement)
{
	//创建一个新节点
	binaryTreeNode> *q = 
		new binaryTreeNode>(std::pair(1, theElement));

	//将新节点与本HBLT进行合并
	this->meld(this->root,q);
	this->treeSize++;
}

erase、clearTree函数

  • erase用来清空整个HBLT树,调用了clearTree函数
template
void maxHblt::erase()
{
	//调用clearTree
	clearTree(this->root);
	this->root = nullptr;
	this->treeSize = 0;
}

template
void maxHblt::clearTree(binaryTreeNode>* t)
{
	//后序遍历删除
	if (t)
	{
		clearTree(t->leftChild);
		clearTree(t->rightChild);
		delete t;
	}
}

initialize函数

  • 根据上面介绍的FIFO初始化HBLT原则,定义了此函数
template
void maxHblt::initialize(T* theElement, int theSize)
{
	//创建一个队列,用来初始化HBLT
	std::queue>*> q;
	//清空当前HBLT
	erase();

	//先建立一组HBLT,每个HBLT中只有一个节点
	for (int i = 1; i <=theSize; ++i)
		q.push(new binaryTreeNode>(std::pair(1, theElement[i])));

	//theSize个HBLT,需要合并theSize-1次
	for (int i = 1; i <= theSize - 1; ++i)
	{
		//从队列中取出两个HBLT进行合并
		binaryTreeNode>* b = q.front();
		q.pop();
		binaryTreeNode>* c = q.front();
		q.pop();
		//合并
		meld(b, c);

		//合并之后再次放入到队列中
		q.push(b);
	}

	if (theSize > 0)
		this->root = q.front();
	this->treeSize = theSize;
}

打印函数

  • 分别为前序遍历、中序遍历、后序遍历
template
void maxHblt::preOrder(binaryTreeNode>* x)const
{
	if (x)
	{
		std::cout << x->element.second<<" ";
		preOrder(x->leftChild);
		preOrder(x->rightChild);
	}
}

template
void maxHblt::inOrder(binaryTreeNode>* x)const
{
	if (x)
	{
		inOrder(x->leftChild);
		std::cout << x->element.second << " ";
		inOrder(x->rightChild);
	}
}

template
void maxHblt::postOrder(binaryTreeNode>* x)const
{
	if (x)
	{
		postOrder(x->leftChild);
		postOrder(x->rightChild);
		std::cout << x->element.second << " ";
	}
}

复杂度分析

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第13张图片

演示案例1

int main()
{
	maxHblt* myMaxHblt = new maxHblt();
	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;
}
  • 演示结果如下图所示:

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第14张图片

  • 根据插入原则,其步骤如下

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第15张图片

  • 当pop一个元素之后(删除根节点10),其步骤如下

C++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第16张图片

演示案例2

int main()
{
	maxHblt* myMaxHblt = new maxHblt();
	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++(数据结构与算法):55---优先级队列的实现(扩充二叉树、高度优先左高树(HBLT)、重量优先左高树(WBLT))_第17张图片

 

你可能感兴趣的:(C++(数据结构与算法))