说明:本文仅供学习交流,转载请标明出处,欢迎转载!
在前面的文章STL之heap相关操作算法中介绍了堆的相关操作算法,由于堆的注意主要作用是用于排序,我们也知道堆排序的时间复杂度为o(nlogn),是一种不稳定的排序算法,利用堆这一数据结构,我们可以很快第获取一个大数据中最大(或最小)的k个数。同时,上篇文章中,也提出了相关heap算法的一些问题。
问题1:在调用push_heap函数实现向堆中插入元素之前,我们必须要先将向底层容器的末端插入该元素,然后才能调用push_heap内部的向上调整来将新元素调整到合适的位置。
问题2:在调用pop_heap函数实现将堆顶元素删除时,我们执行的只是一个伪删除,即并没有将该元素真正从底层容器中删除,只是防止到了容器最后面,而要实现真正的删除,必须在调用pop_heap函数之后,调用底层容器的pop_back操作。
上面两大问题明显地暴露了heap算法的不足,那就是heap算法的操作并不干净利落,它的很多实际的操作需要显示地调用其底层容器的相关函数来实现。其实,heap算法完全可以干净利落地实现插入和删除的操作,一次性完成,但是收之桑榆则失之东隅,如果将这些操作一次性地包装在heap算法里面,那么就无法实现堆排序,毕竟我们的排序目的是获取一个排好序的数组,而不是一个简单的输出。从这个角度来看,我们不难得出这么一个结论:heap算法主要用于堆排序,我们要引入另外一种容器适配器,来弥补heap算法的不足,那么这个适配器非优先级队列priority_queue莫属。
priority_queue的底层容器必须是能够提供随机访问迭代器的容器,所以vector和deque容器可以作为priority_queue的底层容器,而list容器则不行。而priority_queue里面的算法都是基对heap算法和容器操作的进一步封装。再正式给出priority_queue的实现框架之前,我想说明下queue和priority_queue的共同点和主要区别。
在前面的文章 STL之容器适配器queue的实现框架中我给出了容器适配器queue的性质和相关操作,现在我们来对比下priority_queue和queque的相同点与区别。
priority_queue和queue的相同点
1.都不是容器container,而仅仅是容器适配器container adapter。
2.都不提供迭代器,也不支持元素的遍历(在不完全弹出元素的前提下)。
3.都提供empty()、size()、pop()、push(t)四个操作。
4.都只能在队首弹出元素,不能在尾部弹出元素。
5.使用这个两个适配器的头文件均为:#include<queue>
priority_queue和queue的区别
1.priority_queue是一种优先级队列,只能在队首获取元素,且队首元素的优先级最高(此时并不弹出),而queue仅能在队首获取元素,也能在队尾获取元素。即:priority_queue仅有top()函数,而queue既有front()函数,也有back()函数。
2.priority_queue的基础容器必须能够提供随机访问迭代器,因为priority_queue的算法主要调用的是heap相关算法,而heap相关算法中的迭代器都是随机访问迭代器,所以priority_queue的基础容器只能是vector和deque,不能为list。而queue的基础容器要求必须提供首部删除操作pop_front操作,所以queue的基础容器不能是vector,只能是deque和list。
注意:priority_queue和queue的pop操作内部实现是有区别的。queue的pop操作的内部实现简单的依赖其基础容器的pop_front()函数,而priority_queue的pop操作内部实现由两个操作组成:pop_heap()+底层容器的pop_back(),也就是说,priority_queue的pop操作实际上是先将堆顶的元素与末端元素交换,再对新的堆顶元素执行向下调整操作,最后在调用基础容器的pop_back操作实现最高优先级元素的真正删除。
下面给出priority_queue的实现代码和测试代码。
实现代码如下:
#include<iostream> #include<vector>//默认以vector作为底层容器 #include<algorithm>//用到XXX_heap算法 #include<functional>//默认以仿函数less<T>作为优先级比较 using namespace std; template<class T,class Sequence=vector<T>,class Compare=less<typename Sequence::value_type> >//第三个参数只能是仿函数类,不是对象 class priority_queue { public: /*******定义公用访问属性*************/ typedef typename Sequence::value_type value_type;//定义元素类型 typedef typename Sequence::size_type size_type;//定义大小类型 typedef typename Sequence::reference reference;//定义引用类型 typedef typename Sequence::const_reference const_reference;//定义常引用类型 protected: Sequence c;//底层所采用的顺序容器 Compare cmp;//底层所采用的仿函数比较方法 public: priority_queue():c(){}//无参构造函数,调用底层容器的默认构造函数 explicit priority_queue(const Compare& x):c(),cmp(x){}//单形参构造函数,用explicit修饰,防止从形参类型隐式转化为本类类型 template<class InputIterator>//定义成员模板 priority_queue(InputIterator first,InputIterator last):c(first,last)//调用底层容器的范围构造函数,采用默认的比较方法 { make_heap(c.begin(),c.end(),cmp);//利用实参范围创建一个堆 } template<class InputIterator> prioriry_queue(InputIterator first,InputIterator last,const Compare& x):c(first,last),cmp(x)//调用底层容器的范围构造函数,指定比较方法 { make_heap(c.begin(),c.end(),cmp);//建堆函数 } void push(const value_type& x)//插入操作 { c.push_back(x);//先将待插入的元素放置到原堆的末尾 push_heap(c.begin(),c.end(),cmp);//再调用向上调整重新维持堆的性质 } void pop()//删除操作 { pop_heap(c.begin(),c.end(),cmp);//先将堆顶元素与最后一个元素交换,对新交换的堆顶元素做向下调整 c.pop_back();//将元素真正从底层动态数组中删除 } const_reference top()//获取底层容器的第一个元素(即优先级最高的元素) { return c.front(); } size_type size()const//返回当前优先级队列中元素的个数(等于底层容器的大小) { return c.size(); } bool empty()//判断优先级队列是否为空 { return c.empty(); } };
测试代码如下:
int main() { int arr[]={3,7,2,1,4,9,2,7}; priority_queue<int,vector<int>,greater<int> >q(arr,arr+sizeof(arr)/sizeof(int));//我们平时用的普通指针必然是一个随机访问迭代器 cout<<"------------压队列前的元素为----------"<<endl; copy(arr,arr+sizeof(arr)/sizeof(int),ostream_iterator<int>(cout," ") ); cout<<endl; //less<T>表示底层是大顶堆,greater<T>表示底层是小顶堆,很好记呀,堆的类型正好与单词意思相反 /********对初始优先级队列进行操作**********/ cout<<"------------初始操作----------------"<<endl; cout<<"队列的大小:"<<q.size()<<endl; cout<<"队首元素:"<<q.top()<<endl;//队手元素的优先级最高了 /*******top元素出队列以后*************/ q.pop(); cout<<"-------------出一次队列后-------------"<<endl; cout<<"队列的大小:"<<q.size()<<endl; cout<<"队首元素:"<<q.top()<<endl; /******向队列中压入元素-3*************/ q.push(-3); cout<<"------------向队列中压入-3之后-----------"<<endl; cout<<"队列的大小:"<<q.size()<<endl; cout<<"队首元素:"<<q.top()<<endl; /*****依次将队列中现有的元素输出******/ cout<<"-----------依次将队列中现有的元素输出-----"<<endl; while(!q.empty())//由于队列不是容器,只是一种容器适配器,不含迭代器,不能提供单一的遍历操作 { cout<<q.top()<<" ";//只能弹出前访问,依次弹出,再访问才能达到访问所有元素的目的 q.pop(); } cout<<endl; return 0; }
运行结果如下:
参考文献
[1]《STL源码剖析 侯捷》
[2]《C++primer 第4版》