今天将继续加强C++模板类的学习,同时为了巩固已经学习过的数据结构中有关优先队列的知识,我将会使用模板类来实现自己的优先队列。在给出具体实现之前,我要先介绍一下什么是优先队列,聊以为复习吧。
在某些情况下,我们会收集一些元素,处理当前元素的最大值,然后再收集更多数据,再处理此时的最大值。这就要求我们设计的数据结构能够随时访问元素集合中的最大值和能够随时插入数据。优先队列即可以实现这种功能。
优先队列
优先队列的实现有两种思路,第一是在数据插入时即保存数据元素的有序性,这意味着我们能够以O(1)的时间复杂度来访问元素中的最大值。但是我们的在数据进行插入的时候,对寻找数据的合适位置的访问操作的最坏时间复杂度为O(N)。第二种思路是构建一个堆,他能够然我们以N(1)的时间复杂度来访问元素中的最大值,而已O(logN)的时间复杂度来调整堆,以便将元素插入到合适的位置。综上所述,在具体实现优先队列的时候需要根据待处理的元素量级来确定到底使用哪种思路。很明显,当数量级较小的时候,适合使用第一种思路;当数量级较大的时候,第二种思路将比较好。
第一种思路由于实现过程不是很复杂,因此我们的重点放在第二中思路上,首先,简单介绍一下什么是堆。
1、堆是一种是一种数据结构,他是一颗完全二叉树。
2、在堆中,每个父节点都大于等于它的两个孩子节点,被称为堆有序。
3、如果将堆由上到下,由左到右从0开始编号(即根节点编号为0),则某个节点,它的左孩子编号 = (该节点的编号×2+1);右孩子节点的编号 = (该节点编号×2+2)。他的第一个存在孩子节点的节点编号为(堆长度/2 - 1).
优先队列堆实现
在类的定义中,提供了4个构造函数,以及3个具体的数据访问操作和3个元素队列状态的函数。具体如下
class priority_list{ public: priority_list();//默认析构函数 priority_list(int); priority_list(bool (*)(T&,T&));//带比较函数的构造函数 priority_list( int , bool (*)(T&,T&) ); ~priority_list() bool push(const T&);//入队操作 bool pop();//删除队头元素 T top();//获取队头元素 bool is_empty_pl()const;//返回是否为空 int get_size()const;//返回队大小 int get_capacity()const;//返回队容量 private: void heap_up();//向上调整堆 void heap_down();//向下调整堆 void mem_cpy(T*, const T*, int);//元素复制 bool static _compare(T &, T &);//默认的比较函数,表明是大顶堆还是小顶堆 private: T *pt;//数据 int nofns;// 元素个数 int capacity;// 队容量 bool (*compare)(T&,T&);//比较函数 };
这只是我们设计的类提供的接口,具体实现如下
#ifndef PRIORITY_LIST_H_INCLUDED #define PRIORITY_LIST_H_INCLUDED #define DEFAULT_CPTY 32 template<typename T> class priority_list{ public: priority_list()\ :nofns(0), capacity(DEFAULT_CPTY), compare(&_compare){ //默认构造函数 pt = new T[capacity]; if( nullptr == pt ){ //这里申请空间失败,并未对其处理,本该是需要抛出异常的 } // } priority_list(int val)\ :nofns(0), capacity(DEFAULT_CPTY), compare(&_compare){ //构造函数,指定空间大小,但实际的空间大小为2的n次方 while( capacity < val) capacity *= 2;//计算需要申请空间容量大小 pt = new T[capacity];//申请空间 if( nullptr == pt){//如果空间申请失败,抛出异常 } return; } priority_list(bool (*cmp)(T&,T&))\ :nofns(0), capacity(DEFAULT_CPTY), compare(cmp){ //构造函数,指定优先级的排列顺序,默认使用大顶堆 pt = new T[capacity];//申请空间 if( nullptr == pt ){//如果申请空间失败... } return; } priority_list( int val, bool (*cmp)(T&,T&) )\ :nofns(0), capacity(DEFAULT_CPTY), compare(cmp){ //设置队列容量和优先级规则// while( capacity < val) capacity *= 2; pt = new T[capacity];//申请空间 if( nullptr == pt ){//申请空间失败... } return; } ~priority_list(){// 析构函数 if( nullptr != pt){ delete[] pt;//释放申请的空间 pt = nullptr;//置空 } } bool push(const T& t){//入队操作 T *ptt = pt; if( nofns == capacity ){//空间不足,需要重新申请空间 capacity *= 2;//重新申请的空间的大小 pt = new T[capacity]; if( nullptr == pt ){//如果重新申请空间失败 pt = ptt;//恢复原来的空间 capacity /= 2;//恢复原来的容量 return false;//返回失败 }//申请空间成功 obj_cpy(pt, ptt, nofns);//复制nofns到新的空间中 delete[] ptt;//删除旧的空间 } pt[nofns++] = t;//加入队列 heap_up();//向上重新调整堆。 return true; } bool pop(){ if( nofns == 0) return false; if( nofns == 1){ nofns = 0; return true; } pt[0] = pt[nofns-1];//将队尾数据复制到队头, nofns--;//队元素个数减一 heap_down(); //重新向下调整堆 return true; } T top(){ if( nofns <= 0){ //如果队为空,因该抛出异常,但没有写这段代码,因此只能在调用时先判断队是否为空 } return pt[0];//返回队头元素 } bool is_empty_pl()const{ return 0==nofns;//返回队是否为空 } int get_size()const{ return nofns;//返回队元素个数 } int get_capacity()const{ return capacity;//返回队当前容量应该为2的n次方 } private: void heap_up(); void heap_down(); void obj_cpy(T* dest, const T* sour, int n){//复制函数 for(int i = 0; i < n; i++) dest[i] = sour[i]; } bool static _compare(T &t1, T &t2){//默认的比较函数 return t1 < t2; } private: T *pt;//数据 int nofns;// 元素个数 int capacity;// 队容量 bool (*compare)(T&,T&);//比较函数 }; template<typename T> void priority_list<T>::heap_up(){ //当插入队尾后,需要向上重新调整堆结构。 T temp; int itr = nofns-1;//iter指向最后一个元素 while( itr > 0 ){ if( (compare(pt[itr/2], pt[itr])) ){ //同父节点进行比较,如果满足,交换,上浮 temp = pt[itr]; pt[itr] = pt[itr/2]; pt[itr/2] = temp; itr = itr/2;//itr指向他的父节点 continue; } break;//不满足条件,即已经处在对的位置,直接结束 } return; } template<typename T> void priority_list<T>::heap_down(){ //当对头出栈韩,需要将队尾数据移动到队头,向下重新调整堆 T temp; int pitr = 0, citr; while( pitr <= nofns/2 -1 ){ //对一颗完全二叉树来说,第一个存在孩子节点的节点为nofns / 2 - 1; citr = pitr * 2 + 1;//pitr的左孩子节点 if( citr + 1 < nofns && compare(pt[citr], pt[citr+1]))// 如左孩子小于右孩子 citr ++;//如果存在右孩子节点,citr 指向其中满足条件的。 if( (compare(pt[pitr], pt[citr])) ){ //如果孩子节点同父节点满足条件,如父节点小于子节点,交换父子节点 temp = pt[citr]; pt[citr] = pt[pitr]; pt[pitr] = temp; pitr = citr;//继续将pitr指向孩子节点,进行下一次的比较 continue; } break;//如果处在对的位置,直接结束,不需要继续比较下去了 } return; } #endif // PRIORITY_LIST_H_INCLUDED
在我们的代码中存在一些需要改进的地方,在析构函数中,我们并没有对申请空间失败进行额外的处理,还有在top函数中,我们对于空队列的处理存在一些问题。要完美地解决这些问题需要使用到额外的技术--异常处理,但本次实验的重点不在异常这一块,因此我们只是简单地忽略它们。
总结及收获
在本次实验中,我进一步巩固了C++中模板C的定义和使用,同时也复习了数据结构中的优先级队列相关的知识,尤其是大顶堆和小顶堆这一块,这对我后面进一步理解堆排序打下了很好的基础。