C++中的priority_queue笔记【二】
最近几天刷题,总是会看到题解中提到优先队列。
从上一次接触优先队列到现在,没想到已经过了70天了。我还是没有养成用它的好习惯。
C++中的priority_queue笔记
想来无外乎几个原因,一是目前脑子装的东西已经能够实现这一功能。我能用vector再sort解决,就不再能够想着去整什么优先队列。二来是确实没有养成相应的反馈机制。
因此希望通过这篇博客,再将优先队列好好捋一捋。毕竟过了这么久了。70天前的理解,说不定现在看来也存在一些不足。
当然,也会有新的想法。
priority_queue接口规范
//参考来源:http://www.cplusplus.com/reference/queue/priority_queue/?kw=priority_queue
template <class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> > class priority_queue;
c++中优先队列的模板定义:
- 参数类型T
- 底层容器类型
Container
,默认使用vector - 排序规则
Compare
,默认为less
相较于STL的几大件中的containers,优先队列不被单称为一个container,而是称其为container adaptors
,意思是对标准的容器做一些适配工作得到的。(更多的相关知识可以参考侯捷老师的《STL源码剖析》)
但是,其作为容器适配器,也有提供一些接口实现让我们使用、组织数据。
这里,完全可以通过stack进行理解。即优先队列的元素增删操作是
push
以及pop
。然后可以访问队列顶端元素top()
,可以判断是否为空.empty()
,以及当前元素个数.size()
只是在插入元素的过程中,可以依照Compare
重新组织当前元素的次序。
剩下的priority_queue对于基本数据类型以及自定义数据类型的使用相关。在第一篇文章中就有涉及到。
C++中的priority_queue笔记
关键还是在于:排序规则的设定。这也可以参考我的另一篇博客
C++中sort排序以及map自定义排序规则初探
将所有定义排序规则的方式都演示了一遍。
一些想法
如果这篇文章到这里就结束了,可能它的contribution就太少了。所以,我想顺便解决一下心中的一些疑惑。
- 按照之前的逻辑,
set
和multiset
容器似乎已经完全能够承担优先队列的工作。为什么还要有优先队列这一容器适配器呢? - 在刷题过程中,优先队列的应用场景一般是求
前K个最大或最小
的元素。这时候我们采取相应的小顶堆或者大顶堆即可。但是在放置元素的过程中,下面两种方式,有什么区别?priority_queue<int> pq; //1. 每次都将元素插入,然后如果size > k就pop队列顶端元素。 for(int i = 0; i < 2*k; i++){ pq.push(i); if(pq.size() > k) pq.pop(); } //2. 每次插入前都判断一下size,如果size<k直接插入,否则判断 //top和待插入元素的关系。符合条件再插入,否则直接丢弃 for(int i = 0; i < 2*k; i++) { if(pq.size() < k) pq.push(i); else if(pq.top() > i) pq.push(i); }
关于疑惑1的思考
就目前的我的知识面来说,从以下的角度来思考是具有自我说服力的
set容器的底层实现是红黑树,元素的插入删除过程是对数时间复杂度的。
而优先队列是对vector或者deque的适配容器,其push操作其实是调用的底层容器的push_back()
操作。而这一操作是常数时间复杂度的。
因此,set容器在查找的时候具有很大优势。而我们用优先队列的场景,往往只需要关注恰好第K个最大或者最小值
top()
。而之前和之后的元素,都无关紧要。这也是为什么连iterator在优先队列中都被屏蔽掉的原因。
当然,目前的思考也许还有很大的局限性。边学边否定自己,这才是成长嘛
关于疑惑2的验证
进行了两种数据的验证:
- 随机生成的100W的数进行插入删除
- 100W个相同数据的插入删除
//测试优先队列的两种插入写法
#include <queue>
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
void generator(vector<int>& vec, int n){
for(int i = 0; i < n; i++)
{
vec[i] = rand();
}
}
void test01()
{
priority_queue<int> pq1, pq2;
int n = 1000000, k = 100000;
vector<int> vec(n);
for(int i = 0; i < n; i++)
vec[i] = rand();
clock_t s1,e1,s2,e2;
s1 = clock();
for(int i = 0; i < n; i++)
{
pq1.push(vec[i]);
if(pq1.size() > k) pq1.pop();
}
e1 = clock();
s2 = clock();
for(int i = 0; i < n; i++)
{
if(pq2.size() < k) pq2.push(vec[i]);
else if(pq2.top() > vec[i]) pq2.push(vec[i]);
}
e2 = clock();
cout << "total time used by type1:" <<e1 - s1<< endl;
cout << "total time used by type2:" <<e2 - s2<< endl;
}
void test02()
{
priority_queue<int> pq1, pq2;
int n = 1000000, k = 100000;
vector<int> vec(n, 1);
// generator(vec, n); //生成随机数
clock_t s1,e1,s2,e2;
s1 = clock();
for(int i = 0; i < n; i++) //type1
{
pq1.push(vec[i]);
if(pq1.size() > k) pq1.pop();
}
e1 = clock();
s2 = clock();
for(int i = 0; i < n; i++) //type2
{
if(pq2.size() < k) pq2.push(vec[i]);
else if(pq2.top() > vec[i]) pq2.push(vec[i]);
}
e2 = clock();
cout << "total time used by type1:" <<e1 - s1<< endl;
cout << "total time used by type2:" <<e2 - s2<< endl;
}
int main()
{
cout << "test01(): 1000000 random nums: " << endl;
test01();
cout << "test02(): 1000000 same nums(1): " << endl;
test02();
return 0;
}
输出结果:
可以发现:type1在这两种数据集的测试下都花费了更多的时间。因为必有一次插入删除操作。而type2很多时候,用一次if就替代了一次插入和删除操作。
所以我学废了,你呢?