优先级队列

本文介绍了优先级队列的概念及其实现方式,包括大根堆、小根堆和左高树等数据结构,并详细解释了堆排序算法的实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值