一、优先级队列
优先级队列是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();
}