数据结构学习笔记(六)优先级队列、堆和左高树

一、优先级队列
       优先级队列是0个或多个元素的集合,每一个元素都有一个优先权或值,对优先级队列执行的操作有:查找一个元素;插入一个元素;删除一个元素。在最小优先级队列中,查找和删除的元素都是优先级最小的元素;在最大优先级队列中,查找和删除的元素都是优先级最大的元素。优先级队列的元素可以有相同的优先级,对这样的元素,查找和删除可以按照任意顺序处理。优先级队列不同于一般的先进先出的队列的形式。

        以最大优先级队列为例,其C++的抽象类如下:

#pragma once
using namespace std;
//优先级队列的抽象基类
template<class T>
class maxPriorityQueue
{
public:
	virtual ~maxPriorityQueue(){}
	virtual bool empty() const = 0;//判断为空吗
	virtual int size() const = 0;//返回队列的大小
	virtual const T& top() = 0;//返回优先级最大的元素的引用
	virtual void pop() = 0;//删除队列首元素
	virtual void push(const T& theElement) = 0;//添加元素
};
二、线性表描述

        描述最大优先级队列最简单的方法是线性表。采用无序线性表描述时,插入操作很简单,但是查找和删除操作都要遍历整个线性表;采用有序线性表,查找和删除操作之间在表头或者表尾进行,但是插入操作就变得很麻烦。因此描述优先级队列最常用的是堆结构。

三、堆描述

       二叉堆是一种特殊的二叉树,首先它必须是一个完全二叉树(或者近似完全二叉树),我们可以使用数组作为堆的物理存储结构;其次它满足以下性质:最大堆中双亲节点一定比左右孩纸节点要大,最小堆中双亲节点一定比左右孩子要小,并且每一个节点的左右孩纸又组成了一个最大堆(或最小堆)。


        数组下标0不存放数据,从1开始。根据完全二叉树的性质,我们可以很方便的计算双亲结点和孩纸节点的下标之间的关系。

         下图中演示了最大堆的插入操作,图a是一颗5元素的大根堆。当插入一个元素后,为了维持完全二叉树的性质,其树的结构必然为图b所示。整个插入过程是这样的,把新元素插入新节点,然后沿着从新节点到根节点的路径,执行一趟起泡操作,将新元素与其父节点的元素比较交换,直到后者大于等于前者为止。如果新插入的元素是1,那么最终结果如图b;如果插入5,那么因为5大于其父节点2,执行一次起泡,最终结果如图c;如果插入21,由于其大于父节点2,执行一次起泡,而21又比父节点20大,在执行一次起泡,最终结果如图d所示:


          大根堆的删除操作,就是删除根节点的元素,删除之后堆结构要重新组织以维持大根堆的性质。为此,我们把最后一个节点的元素取出,然后删除该节点维持完全二叉树的结构,此时根节点为空,但我们不能把该元素直接放入根节点,我们应该从根节点开始向下逐级寻找位置。在上图12-3(d)中,删除21以后,把最后一个节点2删除,然后寻找元素2应该插入的位置,根节点肯定不行,根的左右孩纸中20比较大,我们将20提前到根节点,然后2放在右孩纸的位置,此时是合适的,最终结果如图12-3图a所示。在下图12-4中(a)模拟了这个过程。接着我们删除根节点20,删除后的树结构如下图b,我们删除最后一个节点10,然后寻找10应该插入的位置,也是从根节点开始找,我们把15提前到根节点,发现10不适合插入左孩纸的位置,继续向下寻找,把14提前,最后结果如图c所示。


            在使用堆前,首先我们应该先建立一个堆,即堆的初始化。假设数组有n=10个元素,在数组a{1:10}中,元素的优先级为【20,12,35,15,10,80,30,17,2,1】。这个数组可以表示为下图12-5(a)的完全二叉树,接下来就要进行堆化。根据完全二叉树的性质,i=n/2是最后一个有孩纸的节点,我们从i开始往前遍历直到根节点1,对每一个节点都要堆化,以维持最大堆的性质。下图中,我们从节点5开始,符合最大堆的性质;接着看节点4,我们把17和15互换,如下图b;接着看3,以此类推。


              整个操作的C++实现如下:

#pragma once
#include<iostream>
#include <sstream>
#include <algorithm>
#include"maxPriorityQueue.h"

using namespace std;

//堆是一个完全二叉树,使用数组存储在时间空间上有好处
template<class T>
class maxHeap :public maxPriorityQueue<T>
{
public:
	//构造函数,复制构造函数和析构函数
	maxHeap(int initialCapacity = 10)
	{
		if (initialCapacity < 1)
			{ cerr << "The Capacity of Heap should be >0"; exit(0); }
		arrayLength = initialCapacity + 1;//从1开始存放树的根节点
		heap = new T[arrayLength];//分配内存空间
		heapSize = 0;
	}
	maxHeap(maxHeap<T>& theHeap)
	{
		if (theHeap.heapSize == 0)
		{
			heap = NULL; arrayLength = heapSize = 0;
		}
		//拷贝
		heap = new T[theHeap.arrayLength];
		copy(theHeap.heap + 1, theHeap.heap + 1 + theHeap.heapSize, heap + 1);
		//复制其他成员
		arrayLength = theHeap.arrayLength;
		heapSize = theHeap.heapSize;
	}
	~maxHeap()
	{
		delete[] heap;
	}
	//成员函数
	bool empty()const{ return heapSize == 0; }
	int size()const{ return heapSize; }
	const T& top();
	void clear();
	void pop();
	void push(const T& theElement);
	void initialize(T *, int);
	void output(ostream& out) const;
private:
	int heapSize;//堆的元素数量
	int arrayLength;//数组的长度+1(下标为0不存数据,从1开始),堆的容量
	T* heap;//数组用来存放堆
};

//返回堆中最大优先级的元素
template<class T>
const T& maxHeap<T>::top()
{
	if (heapSize == 0)
	{	cerr << "The Heap is empty"; exit(0);	}
	return heap[1];
}
//清空整个堆
template<class T>
void maxHeap<T>::clear()
{
	heapSize = arrayLength = 0;
	heap=NULL:
}
//弹出堆中优先级队列最大 的元素
template<class T>
void maxHeap<T>::pop()
{
	if (heapSize == 0)//堆为空
	{	cerr << "The Heap is empty";	exit(0);	}

	//堆不空,删除元素并维持最大堆的性质
	heap[1].~T();//删除最大优先级的元素

	//最后元素为lastElement,然后heapSize-1。删除最后一个元素,保持完全二叉树,此时根节点元素为空
	T lastElement = heap[heapSize --];

	//为元素lastElement寻找位置
	int currentnode = 1, child = 2;
	while (child <= heapSize)
	{
		//找到currentnode的两个孩纸的较大者
		if (child < heapSize && heap[child] < heap[child + 1])
			child++;
		//比较lastElement和child的较大者,如果lastElement大表示可以在currentnode位置处放置lastElement
		if (lastElement >= heap[child])
			break;
		//如果lastElement小,表示不能放在目前的位置,然后将位置下移
		heap[currentnode] = heap[child];
		currentnode = child;
		child *= 2;
	}
	//while循环找到位置,插入
	heap[currentnode] = lastElement;

}
//插入元素
template<class T>
void maxHeap<T>::push(const T& theElement)
{
	//增加容量。
	if (heapSize == arrayLength - 1)
	{
		T* temp = new T[arrayLength * 2];
		copy(heap, heap + arrayLength, temp);
		delete[] heap;
		heap = temp;
		arrayLength *= 2;
	}
	//为元素寻找插入位置
	int currentnode = ++heapSize;//增加一个位置,heapSize的值加1
	while (currentnode != 1 && heap[currentnode / 2] < theElement)
	{
		heap[currentnode] = heap[currentnode / 2];
		currentnode /= 2;
	}
	heap[currentnode] = theElement;//插入元素
}
//初始化堆
template<class T>
void  maxHeap<T>::initialize(T* theHeap,int theSize)
{
	//在数组theheap【1:theSize】中建立大根堆,这只是普通的数组
	delete[] heap;
	heap = theHeap;
	heapSize = theSize;
	//堆化
	for (int root = heapSize / 2; root >= 1; root--)//完全二叉树的性质使然
	{
		T rootElement = heap[root];
		//为元素rootElement寻找位置
		int child = 2 * root;
		while (child <= heapSize)
		{
			if (child < heapSize && heap[child] < heap[child + 1])
				child++;
			if (rootElement >= heap[child])
				break;
			heap[child / 2] = heap[child];
			child *= 2;
		}
		heap[child / 2] = rootElement;
		int textwwww = heap[child / 2];
	}
}
//输出
template<class T>
void maxHeap<T>::output(ostream& out) const
{
	copy(heap + 1, heap + heapSize + 1, ostream_iterator<T>(out, "  "));
}
template <class T>
ostream& operator<<(ostream& out, const maxHeap<T>& x)
{
	x.output(out); return out;
}


测试代码:

#include <iostream>
#include "maxHeap.h"

using namespace std;

void main(void)
{
	// test constructor and push
	maxHeap<int> h(4);
	
	h.push(10);
	h.push(20);
	h.push(5);

	cout << "Heap size is " << h.size() << endl;
	cout << "Elements in array order are" << endl;
	cout << h << endl;

	h.push(15);
	h.push(30);

	cout << "Heap size is " << h.size() << endl;
	cout << "Elements in array order are" << endl;
	cout << h << endl;

	cout << "Copy of the heap:" << endl;
	maxHeap<int> p (h);
	cout << "Heap size is " << p.size() << endl;
	cout << "Elements in in array order are" << endl;
	cout << p << endl;
	
	// test top and pop
	cout << "The max element is " << h.top() << endl;
	h.pop();
	cout << endl << h << endl;
	cout << "The max element is " << h.top() << endl;
	h.pop();
	cout << endl << h << endl;
	cout << "The max element is " << h.top() << endl;
	h.pop();
	cout << endl << h << endl;
	cout << "Heap size is " << h.size() << endl;
	cout << "Elements in array order are" << endl;
	cout << h << endl;
	
	// test initialize
	int z[6];
	for (int i = 1; i < 6; i++)
		z[i] = i;
	h.initialize(z, 5);//调用时出现debug assertion failed的错误。。。。
	cout << "Elements in array order are" << endl;
	cout << h << endl;
}


四、左高树

        使用堆结构,空间利用率高,时间利用率也高,但是它不适合两个优先级队列的合并操作,为了解决这个问题,可以使用左高树来描述优先级队列。

        左高树是一个特殊的二叉树,它有一类特殊的节点叫做外部节点,用以代替树中空子树。其余节点叫做内部节点。增加了外部节点的二叉树被称作扩充的二叉树,图12-6a是一颗二叉树,其相应的扩充二叉树如图12-6b所示。外部节点用阴影框表示。

        定义s(x)是从节点到其子树的外部节点的所有路径中最短的一条。根据s(x)的定义,若x是外部节点,则s的值为0;若x为内部节点,则x的值为左右孩纸的s值的较大者加上1。在图12-6b中的扩充二叉树中各节点的s值如图c所示。接下来,我们可以定义高度优先左高树(HBLT),当且仅当一颗二叉树中任意内部节点的左孩子的s值大于等于右孩纸时,该二叉树为高度优先左高树。下图中的a不是HBLT,因为根据图c,考察外部节点a的父节点,不满足左高树的性质。更进一步,如果一个HBLT同时是一个大根树(树中双亲节点大于孩纸节点),则称之为最大HBLT;若一个HBLT同时还是一个小根树,则称之为最小HBLT。

        如果考虑的不是路径长度,而是节点数目。定义w(x)是以x为根的子树的内部节点数目。根据定义,若x是外部节点,则w值为0;若x为内部节点,则w值为左右孩子的重量之和加1。在图12-6a中的二叉树,各节点w值如图d所示。接下来,定义重量优先左高树(WBLT),当且仅当一颗二叉树中任意内部节点的左孩子的w值大于等于右孩纸时,该二叉树为重量优先左高树。同理,若一颗WBLT同时是大根树,则成为最大WBLT。


         我们可以使用最大HBLT,也可以使用最大WBLT来实现最大优先级队列,两者的实现方法类似,接下来以最大HBLT来进行说明。


          令x为 HBLT 的一个内部节点,则:

                     1)以x为根的子树的节点数目至少为2的s(x)次方减1。

                     2)若以x为根的子树的节点数目有m个节点,那么s(x)最多为log(m+1)。

                     3)从x到一个外部节点的最右路径的长度为s(x)。


          在最大HBLT中,插入和删除操作都可以用合并操作来完成。插入看成原来的左高树,和只有一个节点的左高树进行合并。删除看成去除根节点后,左孩纸和右孩纸的合并。初始化操作也要使用合并来实现,在一个未初始化的数组中【5,1,9,11,2】,首先构造5个单元素的最大HBLT,并形成一个FIFO队列。把最前面的两个最大HBLT(7和1)从队列中删除并合并,结果如图12-8a所示,然后将其加入到队里末尾。下一步重复该步骤,取出(9和11)进行合并,如图b所示,然后将其加入到队列中。以此类推,初始化的结果如图12-8的d所示。


            最大HBLT最关键的是合并操作。我们用递归来实现。令A,B为需要合并的两个最大HBLT。若一个为空,则另一个就是合并后的结果。假设两者均不为空,先比较两个根元素,较大者作为合并后的根。假定A的根较大,且左子树为L。令C为A的右子树R和B合并后的结果。那么A和B合并后的结果是以A为根,以L和C为子树的最大HBLT。如果L的s值小于C的值,那么两者的左右顺序需要交换。

           在下图中,图a中两颗单元素的最大HBLT,显然9是合并后的根,因为9的右子树为空,则右子树和7合并后还是7,因此合并后变为图b,又因为根9的右孩子的s值大于左孩纸,因此左右孩子互换,最终结果为图c。接下来合并图d的的两个元素,10作为根节点,其右子树和7合并后还是7,最终合并后变成图e,此时左右孩子的s值符合要求,因此不需要互换。然后合并图 f 的两个HBLT,同理18作为根节点,其右孩子和另一个HBLT合并后的结果(图e),最终合并结果为图g,最后交换左右子树的顺序变为图h。最后,我们合并图 i 的两个HBLT,同理40为根节点,其右孩纸和另一个HBLT合并的结果(图h),最终合并结果为图 j ,最后交换左右孩子的顺序,变为图 k。


       整个用最大HBLT实现优先级队列的C++实现如下:

#pragma once
#include<iostream>
#include<utility>
#include<deque>
#include"maxPriorityQueue.h"
#include"binaryTree.h"

using namespace std;


//最大高度优先左高树=最大堆+高度优先左高树(二叉树)
template<class T>
class maxHBLT :public maxPriorityQueue<T>, public binaryTree<pair<int,T>> 
{
public:
	//构造函数、复制构造函数和析构函数
	maxHBLT(){ root = NULL; theSize = 0; }
	maxHBLT(const maxHBLT<T>& theHBLT) :binaryTree(theHBLT){}
	~maxHBLT(){ erase(); }

	//成员函数
	bool empty()const{ return theSize == 0; }
	int size() const { return theSize; }
	const T& top();
	void push(const T& );
	void pop();
	void initialize(T *, int);
	void meld(maxHBLT<T>& theHblt);
	void output(){ postOrder(hbltOutput); cout << endl; }

private:
	//合并以*x和*y为根的两颗左高树,合并后的左高树以x为根,返回x的指针
	void meld(TreeNode<pair<int, T> >* &x, TreeNode<pair<int, T> >* &y)
	{
		//y为空,直接返回x即可(x空不空都一样)
		if (y == NULL) return;
		//y不为空,x为空,直接返回y即可,需要将y赋给x
		if (x == NULL) { x = y; return; }
		//x和y都不空时,
		if (x->element.second < y->element.second)//步骤一,x和y的树根节点中较大的作为合并后堆的根节点,这里都赋给x的
		{
			swap(x, y);
		}
		meld(x->rightchild, y);//步骤二,合并x的右子树和y,返回为x的右子树
		if (x->leftchild == NULL)//步骤三,若左子树比右子树矮,交换左右子树
		{
			x->leftchild = x->rightchild;
			x->rightchild = NULL;
			x->element.first = 1;
		}
		else
		{
			if (x->leftchild->element.first < x->rightchild->element.first)
				swap(x->leftchild, x->rightchild);
			x->element.first = x->rightchild->element.first + 1;
		}
	}
	static void hbltOutput(TreeNode<pair<int, T> > *t)
	{
		cout << t->element.second << ' ';
	}
};

//返回最大左高树中最高优先级的元素
template<class T>
const T& maxHBLT<T>::top()
{
	if (theSize == 0){ cerr << "The HBLT is empty"; exit(0); }
	return root->element.second;
}
//添加元素,
template<class T>
void maxHBLT<T>::push(const T& theElement)
{
	//新建一个只有一个节点的左高树
	TreeNode<pair<int, T>> *q = new TreeNode<pair<int, T>>(pair<int, T>(1, theElement));
	//合并两个左高树
	meld(root, q);
	theSize++;
}
//删除优先级最高的元素
template<class T>
void maxHBLT<T>::pop()
{
	if (root == NULL){ cerr << "The HBLT is empty"; exit(0); }
	TreeNode<pair<int, T>>* left = root->leftchild,	*right = root->rightchild;

	delete root;
	root = left;
	meld(root, right);
	theSize--;
}
//初始化,使用数组theElement【1:Size】建立左高树
template<class T>
void maxHBLT<T>::initialize(T* theElement, int Size)
{
	deque<TreeNode<pair<int, T>>*> q;
	erase();//清空*this

	//为每一个节点建立一个左高树
	for (int i = 1; i <= Size; i++)
		q.push_back(new TreeNode<pair<int, T>>(pair<int, T>(1, theElement[i])));
	//开始合并
	for (int i = 1; i <= Size - 1; i++)
	{
		TreeNode<pair<int, T>> *f1 = q.front();
		q.pop_front();
		TreeNode<pair<int, T>> *f2 = q.front();
		q.pop_front();
		meld(f1, f2);
		q.push_back(f1);
	}
	if (Size > 0)
		root = q.front();
	theSize = Size;
}
//合并this和指定的一个左高树
template<class T>
void maxHBLT<T>::meld(maxHBLT<T>& theHblt)
{
	meld(root, theHblt.root);
	theSize += theHblt.theSize;
	theHblt.root = NULL;
	theHblt.theSize = 0;
}


        测试代码:

// test max height biased leftist tree class
#include <iostream>
#include "maxHBLT.h"

using namespace std;

void main(void)
{
	maxHBLT<int> h;
	int a[6] = { 0, 7, 9, 1, 8, 11 };
	h.initialize(a, 5);
	cout << "One tree in postorder is" << endl;
	cout << "Tree size is " << h.size() << endl;
	h.output();

	maxHBLT<int> j(h);
	//直接使用j=h不行,没有重载=运算符,这不是复制构造函数
	cout << "Other tree in postorder is" << endl;
	cout << "Tree size is " << j.size() << endl;
	j.output();

	h.meld(j);
	cout << "After melding, the tree in postorder is" << endl;
	cout << "Tree size is " << h.size() << endl;
	h.output();

	int w = h.top();
	h.pop();
	int x = h.top();
	h.pop();
	int y = h.top();
	h.pop();
	int z = h.top();
	h.pop();
	cout << "After popping four elements, the tree is" << endl;
	cout << "Tree size is " << h.size() << endl;
	h.output();
	cout << "The popped elements, in order, are" << endl;
	cout << w << "  " << x << "  " << y << "  " << z << endl;

	h.push(10);
	h.push(20);
	h.push(5);
	cout << "After pushing 10, 20 & 5, the tree is" << endl;
	cout << "Leftist tree in postorder" << endl;
	cout << "Tree size is " << h.size() << endl;
	h.output();

	h.push(15);
	h.push(30);
	h.push(2);
	cout << "After pushing 15, 30 & 15, the tree is" << endl;
	cout << "Leftist tree in postorder" << endl;
	cout << "Tree size is " << h.size() << endl;
	h.output();

	cout << "The max element is " << h.top() << endl;

	h.pop();
	cout << "Popped max element " << endl;
	cout << "Leftist tree in postorder" << endl;
	cout << "Tree size is " << h.size() << endl;
	h.output();

	x = h.top();
	h.pop();
	cout << "Popped max element " << x << endl;
	cout << "Tree size is " << h.size() << endl;
	cout << "Leftist tree in postorder" << endl;
	h.output();
}


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值