数据结构与算法——优先级队列(C++)

优先级队列

优先级队列是一种循优先级访问的不失高效的轻量级数据结构,优先级队列只需要维持偏序即可满足设计目标。优先级队列的实现依托于向量的“肉”和二叉树的“灵”,完全二叉堆与左式堆是优先级队列实现的两种方式。

接口功能
insert( T )按照优先级次序插入词条
getMax( )取出优先级最高的词条
delMax( )删除优先级最高的词条

1 完全二叉堆

堆序性:堆顶之外的每个节点都不高于其父节点。以下是完全二叉堆的实现程序。

1.1 完全二叉堆的实现

#include "../dsa_vector_200622/dsa_vector.h" //借助多重继承机制,基于向量
#include "pq_macro.h"
#include "pq.h" //按照优先级队列ADT实现的
template <typename T> class PQ_ComplHeap : public PQ<T>, public Vector<T> { //完全二叉堆
//    /*DSA*/friend class UniPrint; //演示输出使用,否则不必设置友类
protected:
   Rank percolateDown ( Rank n, Rank i ); //下滤
   Rank percolateUp ( Rank i ); //上滤
   void heapify ( Rank n ); //Floyd建堆算法
public:
   PQ_ComplHeap() { } //默认构造
   PQ_ComplHeap ( T* A, Rank n ) { this->copyFrom ( A, 0, n ); heapify ( n ); } //批量构造
   void insert ( T ); //按照比较器确定的优先级次序,插入词条
   T getMax(); //读取优先级最高的词条
   T delMax(); //删除优先级最高的词条
}; //PQ_ComplHeap

//getMax()
template <typename T> T PQ_ComplHeap<T>::getMax() {  return this->_elem[0];  }


//insert
template <typename T> void PQ_ComplHeap<T>::insert ( T e ) { //将词条插入完全二叉堆中
   Vector<T>::insert ( e ); //首先将新词条接至向量末尾
   percolateUp ( this->_size - 1 ); //再对该词条实施上滤调整
}


void swap( int &a, int &b){
   int tmp = a;
   a = b;
   b = tmp;
}
//上滤
//对向量中的第i个词条实施上滤操作,i < _size
template <typename T> Rank PQ_ComplHeap<T>::percolateUp ( Rank i ) {
   while ( ParentValid ( i ) ) { //只要i有父亲(尚未抵达堆顶),则
      Rank j = Parent ( i ); //将i之父记作j
      if (  this->_elem[i] <= this->_elem[j]  ) break; //一旦当前父子不再逆序,上滤旋即完成
      swap ( this->_elem[i], this->_elem[j] ); i = j; //否则,父子交换位置,并继续考查上一层
   } //while
   return i; //返回上滤最终抵达的位置
}

//delMax()
template <typename T> T PQ_ComplHeap<T>::delMax() { //删除非空完全二叉堆中优先级最高的词条
   T maxElem = this->_elem[0]; this->_elem[0] = this->_elem[ --this->_size ]; //摘除堆顶(首词条),代之以末词条
   percolateDown ( this->_size, 0 ); //对新堆顶实施下滤
   return maxElem; //返回此前备份的最大词条
}

//对向量前n个词条中的第i个实施下滤,i < n
template <typename T> Rank PQ_ComplHeap<T>::percolateDown ( Rank n, Rank i ) {
   Rank j; //i及其(至多两个)孩子中,堪为父者
   while ( i != ( j = ProperParent ( this->_elem, n, i ) ) ) //只要i非j,则
      { swap ( this->_elem[i], this->_elem[j] ); i = j; } //二者换位,并继续考查下降后的i
   return i; //返回下滤抵达的位置(亦i亦j)
}

//Floyd建堆
template <typename T> void PQ_ComplHeap<T>::heapify ( Rank n ) { //Floyd建堆算法,O(n)时间
   for ( int i = LastInternal ( n ); InHeap ( n, i ); i-- ) //自底而上,依次
/*DSA*/
      percolateDown ( n, i ); //下滤各内部节点
// /*DSA*/for ( int k = 0; k < n; k++ ) {
// /*DSA*/  int kk = k; while ( i < kk ) kk = (kk - 1) / 2;
// /*DSA*/  i == kk ? print(_elem[k]) : print("    " );
// /*DSA*/}; printf("\n");
// /*DSA*/}
}

template <typename T> void Vector<T>::heapSort (  Rank lo, Rank hi ) { //0 <= lo < hi <= size
   PQ_ComplHeap<T> H ( _elem + lo, hi - lo ); //将待排序区间建成一个完全二叉堆,O(n)
   while ( ! H.empty() ) //反复地摘除最大元并归入已排序的后缀,直至堆空
      _elem[--hi] = H.delMax(); //等效于堆顶与末元素对换后下滤
}

其包含的头文件如下"…/dsa_vector_200622/dsa_vector.h"

//"pq.h"
#pragma once

template <typename T> struct PQ { //优先级队列PQ模板类
   virtual void insert ( T ) = 0; //按照比较器确定的优先级次序插入词条
   virtual T getMax() = 0; //取出优先级最高的词条
   virtual T delMax() = 0; //删除优先级最高的词条
};
//"pq_macro.h"
#pragma once
#define  InHeap(n, i)      ( ( ( -1 ) < ( i ) ) && ( ( i ) < ( n ) ) ) //判断PQ[i]是否合法
#define  Parent(i)         ( ( i - 1 ) >> 1 ) //PQ[i]的父节点(floor((i-1)/2),i无论正负)
#define  LastInternal(n)   Parent( n - 1 ) //最后一个内部节点(即末节点的父亲)
#define  LChild(i)         ( 1 + ( ( i ) << 1 ) ) //PQ[i]的左孩子
#define  RChild(i)         ( ( 1 + ( i ) ) << 1 ) //PQ[i]的右孩子
#define  ParentValid(i)    ( 0 < i ) //判断PQ[i]是否有父亲
#define  LChildValid(n, i) InHeap( n, LChild( i ) ) //判断PQ[i]是否有一个(左)孩子
#define  RChildValid(n, i) InHeap( n, RChild( i ) ) //判断PQ[i]是否有两个孩子
#define  Bigger(PQ, i, j)  (  PQ[i] <=  PQ[j]  ? j : i ) //取大者(等时前者优先)
#define  ProperParent(PQ, n, i) /*父子(至多)三者中的大者*/ \
            ( RChildValid(n, i) ? Bigger( PQ, Bigger( PQ, i, LChild(i) ), RChild(i) ) : \
            ( LChildValid(n, i) ? Bigger( PQ, i, LChild(i) ) : i \
            ) \
            ) //相等时父节点优先,如此可避免不必要的交换

1.2 完全二叉堆的测试

以下是对于完全二叉堆的测试,依次测试了建堆函数、getMax、delMax与堆排序。结果见注释,可知见测试成功。

#include "pq_complheap.h"
#include <iostream>
using namespace std;
int main(){
    int A[] = { 4, 2, 5, 1, 3};
    //测试建堆函数
    PQ_ComplHeap<int> a (A, 5);
    //测试getMax
    cout << a.getMax() << endl;//5
    //测试insert
    a.insert( 6 );
    //测试delMax
    cout << a.delMax() << endl;//6
    cout << a.getMax() << endl;//5

    //测试堆排序
    Vector<int> vec(A, 5);
    vec.heapSort( 0, 5);
    for( int i= 0; i<5; ++i )
        cout << vec[i] << " ";//1 2 3 4 5 
    
}

2 左式堆

  • 由于完全二叉堆不能高效地完成堆的合并,所以提出左式堆的实现方法。

  • 左式堆的基本思路是:在保持堆序性的前提下附加新的条件,使得在堆的合并过程中,只需调整很少量的节点。

  • 空节点路径长度: npl(null path length)。npl(x)既等于x到外部节点的最近距离,同时也等于以x为根的最大满子树的高度。

  • 左式堆:任意节点x,满足npl( lc(x) ) >= npl( rc(x) )

  • 最右侧通路:rPath( x )。从x沿着右侧分支一直前行到空节点,每个点的npl值等于其最右侧通路的长度。r为根节点,rPath( r )终点是深度最小的外部节点。

2.1 左式堆的实现

左式堆的核心是merge的实现方法,insert和delMax的实现都依赖于merge。

#pragma once

#include "pq.h" //引入优先级队列ADT
#include "../dsa_bintree_20200720/bintree.h" //引入二叉树节点模板类

template <typename T>
class PQ_LeftHeap : public PQ<T>, public BinTree<T> { //基于二叉树,以左式堆形式实现的PQ
   /*DSA*/friend class UniPrint; //演示输出使用,否则不必设置友类
public:
   PQ_LeftHeap() { } //默认构造
   PQ_LeftHeap ( T* E, int n ) //批量构造:可改进为Floyd建堆算法
   {  for ( int i = 0; i < n; i++ ) insert ( E[i] );  }
   void insert ( T ); //按照比较器确定的优先级次序插入元素
   T getMax(); //取出优先级最高的元素
   T delMax(); //删除优先级最高的元素
}; //PQ_LeftHeap

//getMax
template <typename T> T PQ_LeftHeap<T>::getMax() //获取非空左式堆中优先级最高的词条
{ return this->_root->data; } //按照此处约定,堆顶即优先级最高的词条

//swap
void swap( BinNodePosi(int) &a,BinNodePosi(int) &b){
   auto tmp = a;
   a = b;
   b = tmp;
}
//merge
template <typename T> //根据相对优先级确定适宜的方式,合并以a和b为根节点的两个左式堆
static BinNodePosi(T) merge ( BinNodePosi(T) a, BinNodePosi(T) b ) {
   if ( ! a ) return b; //退化情况
   if ( ! b ) return a; //退化情况
   if (  a->data <= b->data  ) swap ( a, b ); //一般情况:首先确保b不大
   a->rc = merge ( a->rc, b ); //将a的右子堆,与b合并
   a->rc->parent = a; //并更新父子关系
   if ( !a->lc || a->lc->npl < a->rc->npl ) //若有必要
      swap ( a->lc, a->rc ); //交换a的左、右子堆,以确保右子堆的npl不大
   a->npl = a->rc ? a->rc->npl + 1 : 1; //更新a的npl
   return a; //返回合并后的堆顶
} //本算法只实现结构上的合并,堆的规模须由上层调用者负责更新

//delMax
template <typename T> T PQ_LeftHeap<T>::delMax() { //基于合并操作的词条删除算法(当前队列非空)
   BinNodePosi(T) lHeap = this->_root->lc; //左子堆
   BinNodePosi(T) rHeap = this->_root->rc; //右子堆
   T e = this->_root->data; delete this->_root; this->_size--; //删除根节点
   this->_root = merge ( lHeap, rHeap ); //原左右子堆合并
//   if ( _root ) _root->parent = NULL; //若堆非空,还需相应设置父子链接
   return e; //返回原根节点的数据项
}

//insert
template <typename T> void PQ_LeftHeap<T>::insert ( T e ) { //基于合并操作的词条插入算法
   BinNodePosi(T) v = new BinNode<T> ( e ); //为e创建一个二叉树节点
   this->_root = merge ( this->_root, v ); //通过合并完成新节点的插入
//   _root->parent = NULL; //既然此时堆非空,还需相应设置父子链接
   this->_size++; //更新规模
}

2.2 左式堆的测试

左式堆的测试程序如下所示,测试项目包括getMax,delMax,insert与merge,结果如注释所示,可见测试成功。

#include "pq_leftheap.h"
#include <iostream>
using namespace std;

int main(){
    int A[] = { 2, 5, 1, 7, 18};
    PQ_LeftHeap<int> lh1( A, 5);
    //getMax()
    cout << lh1.getMax() << endl;//18
    //delMax
    cout << lh1.delMax() << endl;//18
    //insert
    lh1.insert( 11 );
    cout << lh1.getMax() << endl;//11

    //merge
    int B[] = { 12, 5, 21, 6, 9};
    PQ_LeftHeap<int> lh2(B, 5);
    //PQ_LeftHeap<int> lh( merge( lh1.root() , lh2.root()));
    cout << merge( lh1.root() , lh2.root())->data << endl;//21
    cout << lh2.delMax() << endl;//21
    cout << lh2.delMax() << endl;//12
    cout << lh2.delMax() << endl;//11,可见lh2合并入lh2中了
    cout << lh2.delMax() << endl;//9
    cout << lh2.delMax() << endl;//7
}

3 优先级队列总结

优先级队列是一个轻量级的实现“循优先级”访问的数据结构,它包括完全二叉堆与左式堆两种实现形式。其中,完全二叉堆可以高效地完成各项操作,但是merge操作仍然有待改进;所以,为提高merge的效率,左式堆被提了出来,基于merge的insert和delMax也被设计出来。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值