1 概述
与FIFO结构的队列不同,在优先级队列当中,元素出队列的顺序是由元素的优先级决定。可以按照优先级的递增顺序,也可以按优先级的递减顺序,但不是元素进入队列的顺序。
堆是实现优先级队列的效率很高的数据结构。C++的STL类priority-queue是用堆实现的优先级队列。下面主要介绍堆和左高树。
2 定义
优先级队列(priority queue)是0个或多个元素的集合,每个元素都有一个优先权或值,对优先级队列进行的操作有:
1)查找一个元素;
2)插入一个新元素;
3)删除一个元素。
与这些操作分别对应的函数是top、push和pop。在最小优先级队列中(min priority queue)中,查找和删除的元素都是优先级最小的元素;在最大优先级队列(max priority queue)当中,查找和删除的元素都是优先级最大的元素。优先级队列的元素可以有相同的优先级,对这样的元素,查找和删除可以按照任意顺序处理。
举例:假设我们对一台机器所提供的服务按固定时间收费(如,按天或按月)。每个用户的付费是相同的。假设机器在任何时候都可以服务,为了获得最大的收益,我们把等待机器服务的用户组成一个最小的优先级队列。优先级是用户所需的服务时间。当一个用户需要机器服务时,他的请求就被加到优先级队列。一旦机器空闲,服务时间需求最小(即优先级最大时)的用户便最先得到服务。
3 抽象数据类型
最大优先级队列的抽象数据类型说明如下:
抽象数据类型 maxPriorityQueue
{
实例
有限个元素集合,每一个元素都有优先级
操作
empty():返回值为true,当且仅当队列为空
size():返回队列的元素个数
top():返回优先级最大的元素
pop():删除优先级最大的元素
push(x):插入元素x
}
下面的程序是C++抽象数据类
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;
};
4 堆
4.1 定义
1.一棵大根树(小根树)是这样一棵树,其中每个节点的值都大于(小于)或等于其子节点(如果有子节点的话)的值。
2.一个大根堆(小根堆)既是大根树(小根树)也是完全二叉树。
4.2 大根堆的插入
插入策略:从一个叶子到根进行一次起泡过程。每一次的操作需要耗时O(1)。因此,实现这种插入策略的时间复杂性为O(height)=O(log n)。
程序如下(复杂度为O(log n)):
template<class T>
void maxHeap<T>::push(const T& theElement)
{
if(heapSize==arrayLength-1)//heapSize是堆元素的个数
{//数组长度加倍
changeLength1D(heap,arrayLength,2*arrayLength);
arrayLength*=2;
}
int currentNode=++heapSize;
while(currentNode!=1 && heap[currentNode/2]<theElement)
{
heap[currentNode]=heap[currentNode/2];
currentNode/=2;
}
heap[currentNode]=theElement;
}
4.3 大根堆的删除
删除策略:
1、删除要删除的节点的值;
2、确定删除后的结构为完全二叉树,并保存多余的节点的值a;
3、确定a的位置:沿着删除节点到a原先的位置的路径,不断调整节点的值,确保其为大根堆。
程序如下(删除最大元素,复杂度为O(log n)):
template<class T>
void maxHeap::pop()
{
if(heapSize==0)
throw queueEmpty();
heap[1].~T();//删除最大元素
//重建堆
T lastElement=heap[heapSize--];//保存最后一个元素
int currentNode=1,child=2;
while(child<=heapSize)
{
//heap[child]应该是currentNode的更大的孩子
if(child<heapSize && heap[child]<heap[child+1])
child++;
//可以把lastElement放在heap[currrentNode]中吗?
if(lastElement>=heap[child])
break;//可以
//不可以
heap[currentNode]=heap[child];
currentNode=child;
child*=2;
}
heap[currentNode]=lastElement;//放进去吧
}
4.4 大根堆的初始化
初始化策略:从最后个具有孩子节点的地方进行检查,如果以这个元素的为根的子树是大根堆,则不做操作。如果不是大根堆则进行调整为大根堆,然后依次检查上一个节点为根的子树是否是大根堆,直至检查到根节点为止
程序如下(复杂度为O(n)):
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)
{
//heap[child]应该是兄弟中较大的一个
if(child<heapSize && heap[child]<heap[child+1])
child++;
//可以把元素rootElement放在heap[child/2]中吗?
if(rootElement>=heap[child])
break;//可以
//不可以
heap[child/2]=heap[child];
child*=2;
}
heap[child/2]=rootElement;//放进去吧
}
}
5 左高树
5.1 定义
1、一棵二叉树称为高度优先左高树(HBLT),当且仅当其任何一个内部节点的左孩子的s值都大于或等于右孩子的s值。令s(x)是从节点x到其子树的外部节点的所有路径中最短的一条,若x是外部节点,则s的值为0;若x为内部节点,则s的值为:min{s(L),s(R)}+1
2、若一棵HBLT同时还是大根树,则称最大HBLT。若一棵HBLT同时还是小根树,则称最小HBLT。
5.2 用途
当两个优先级队列或多个长度不同的队列需要合并的时候,就用到左高树了
5.3 最大HBLT的插入
插入策略:最大HBLT的插入操作可以利用最大HBLT的合并操作来实现。假定将元素x插入到名为H的最大HBLT当中,则先建立一棵新的只含这个元素的HBLT,然后和原来的HBLT合并
template <class T>
void maxHblt<T>::push(const T& theElement)
{
binaryTreeNode<pair<int,T>>*q=new binaryTreeNode<pair<int,T>(pair<int,T>(1,theElement));
meld(root,q);//合并
treeSize++;
}
5.4 最大HBLT的删除
删除策略:最大元素在根中,因此根被删除可以看做是两个左右子树的合并。
template<class T>
void maxHblt<T>::pop()
{
if(root==NULL)
throw queueEmpty();
binaryTreeNode<pair<int,T>> *left=root->leftChild,
*right=root->rightChild;
delete root;
root=left;
medl(root,right)
treeSize--;
}
5.5 最大HBLT的合并
合并策略:用递归实现。
1、首先比较两个树的根的大小,将大的根作为合并后的根;
2、将大的根的树右子树与小的根的树进行合并,合并后的树作为其新的右子树;
3、判断是否满足最大HBLT的定义,若不符合则进行左右翻转。
合并两棵左高树程序:
template<class T>
void maxHblt<T>::meld(binaryTreeNode<pair<int,T>>* &x,
binaryTreeNode<pair<int,T>>* &y)
{
if(y==NULL)
return 0;
if(x==NULL)
{x=y;return 0;}
if(x->element.second<y->element.second)//确保x为较大的根并成为新根
swap(x,y);
meld(x->rightChild,y);//x的右孩子和y合并
if(x->leftChild==NULL)
{
x-leftChild=x->rightChild;
x-rightChild=NULL;
x->element.first=1;//s的值
}
else
{
if(x->leftChild->element.first<x->rightChild->element.fist)
swap(x->leftChild,x->rightChild);
x->element.first=x->rightChild->element.fist+1;
}
}
template<class T>
void maxHblt<T>::meld(maxHblt<T>& theHblt)
{
meld(root,theHblt.root);
treeSize+=theHblt.treeSize;
theHblt.root=NULL;
theHblt.treeSize=0;
}
5.6 最大HBLT的初始化
初始化策略:将n个元素逐个插入最空的最大HBLT当中,首先创建n个仅含有一个元素的最大HBLT,这n个树组成一个FIFO的队列,然后成对删除HBLT,将其合并再插入到队列末尾,直到队列只有一棵HBLT为止。
template<class T>
void maxHblt<T>::initialize(T* theElements,int theSize)
{
arrayQueue<binaryTreeNode<pair<int,T>>*> q(theSize);
erase();
for(int i=1,i<=theSize;i++)
q.push(new binaryTreeNode<pair<int,T>>(pair<int,T>(1,theElements[i])));
for(i=1;i<=theSize-1;i++)
{
binaryTreeNode<pair<int,T>> *b=q.front();
q.pop();
binaryTreeNode<pair<int,T>> *c=q.front();
q.pop();
meld(b,c);
q.push(b);
}
if(theSize>0)
root=q.front();
treeSize=theSize;
}
6 堆排序
deactivateArray将maxHeap::heap置为NULL。这一步是必要的,因为maxHeap::initialize置maxHeap::heap为数组a,当堆排序函数退出时,大根堆析构函数将删除maxHeap::heap。因此,为防止数组a被删除,需要调用deactivateArray
template <class T>
void heapSort(T a[],int n)
{
//在数组上建立大根堆
maxHeap<T> heap(1);
heap.initialize(a,n);
for(int i=n-1;i>=1;i--)
{
T x=heap.top();
heap.pop();
a[i+1]=x;
}
heap.deactivateArray();//从堆的析构函数中保存数组a
}