priority_queue 基本介绍
priority_queue就是优先级队列。其头文件就是queue,但是队列和优先级队列关系不大,两个是不同的数据结构。但二者都是适配器,容器适配器。
优先级队列中存放的数据是有优先级的。
其内部有以下成员方法,其中常用的就empty, size, top, push, pop。
直接演示一下常用方法的使用:
我们看到用法几乎是与栈和队列一样。但是这里打印结果是排好序了(降序)。所以,优先级队列默认情况下是大的优先。
优先级队列的适配器
看其第二个模板参数:class Container = vector,这就是容器适配器。因此我们可以将其底层的容器改为其他的容器(list不行):
第三个模板参数compare
跟sort用法很像,sort第三个参数传的是一个对象,比如说给sort传greater()就是降序,而这里传的是类型,比如说传greater就是小堆。可以看到,模板参数缺省值为less,可能有的同学不知道value_type是啥,其实就是我们日常放在容器中的元素类型,就是那个T,T可以为int、char什么的都行。所以我们默认情况下参数 Compare 就是less的类型,那么就是大堆。
这样就变成了小堆,每次取堆顶的值就是最小值。
- 传greater时是小堆。
- 传less时是大堆。
使用优先级队列来解决这个问题:数组中第K个最大的元素
题目中有要求,必须设计时间复杂度为O(N)的算法。那么先进行排序操作的同学就另寻他路吧。这道题就可以用到优先级队列,就是堆。先把堆建好,然后pop k-1次后的堆顶就是第k大的元素。
class Solution
{
public:int findKthLargest(vector<int>& nums, int k) {priority_queue<int> pq(nums.begin(), nums.end());while(--k){pq.pop();}return pq.top();}
};
priority_queue 的模拟实现
堆的实现的核心无非就是向上调整和向下调整。而堆虽然逻辑结构上是二叉树,但是实际物理结构就是数组。我们用C++写,默认容器就是vector,因为随机访问数据的次数比较多。我们很多地方就可以直接复用vector中的函数接口,所以就需要自己动手写两个,一个是向上调整,一个是向下调整。
#include<vector>
#include<algorithm>
#include<iostream>namespace Flash
{template<class T, class Container = std::vector<T>,class Compare = less<T>>class priority_queue{private://向上调整void adjust_up(size_t child){Compare com;size_t parent = (child - 1) / 2;while (child > 0){//if (_con[child] > _con[parent])//if (_con[parent] < _con[child])if (com(_con[parent], _con[child])){std::swap(_con[child], _con[parent]);}else{break;}child = parent;parent = (child - 1) / 2;}}//向下调整void adjust_down(size_t parent){Compare com;size_t child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && com(_con[child], _con[child + 1])){++child;}//if (_con[parent] < _con[child])if (com(_con[parent], _con[child])){std::swap(_con[child], _con[parent]);}else{break;}parent = child;child = parent * 2 + 1;}}public:void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}void pop(){std::swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}const T& top(){return _con[0];}bool empty()const{return _con.empty();}int size()const{return _con.size();}private:Container _con;};template<class T>struct less{bool operator()(const T& L, const T& R){return L < R;}};template<class T>struct greater{bool operator()(const T& L, const T& R){return L > R;}};}
上面基本功能的是实现了,但是问题是不能控制大小堆,那我们可以像库中那样,再搞一个模板参数,传一个仿函数来实现大小堆的控制。
仿函数
仿函数就是一个类,里面重载了()运算符。
第一个ls(1, 2)乍一看就像是函数调用,但实际上就是类匿名对象调用了operator()。
再来个greater:
这就是仿函数,用类来重载()来实现。调用的时候就像函数一样,在C语言阶段学过的qsort,传比较的那个参数的时候要传函数指针,但是函数指针太麻烦了,所以C++为了不再用函数指针,就搞了仿函数。
那么此时我们就可以搞第三个模板参数了。
传的是类型。然后把我们向上调整和向下调整中的代码改一改:
上面的priority_queue、less、greater都是在一个命名空间FangZhang中的,所以除了vector是复用的,剩下的都是手写的,less和greater就是刚写出来的那两个,可以直接用。
再来强调一点:
假如说一个对象vector v,我们用sort时,传参是sort(v.begin(), v.end(), less()),而这堆这是定义对象传模板参数priority_queue<int, vector, less>,前者是传匿名对象,后者是传类型。是不一样的。不要搞混。
栈和队列还有优先级队列都是容器适配器,就是可以改变其底层所使用的容器,从而能够用不同的容器来实现其底层的函数接口。