优先级队列
- 前言
- 正式开始
- priority_queue 基本介绍
- 优先级队列的适配器
- 第三个模板参数compare
- 模拟实现priority_queue
- 仿函数
前言
点进来的小伙伴不知道学过数据结构里的堆没有,如果学过的话,那就好说了,优先级队列就是堆,如果没学过,没关系,可以参考一下我之前写的一篇关于堆的博客,可以点进去看看:【数据结构】堆(包含堆排序和TOPK问题)
那么了解过堆了的话,我就不讲那么细致了,就把STL中给的接口说一遍,然后模拟实现一下,再说一下适配器就好了。
正式开始
还是cpluplus这个网站:queue
priority_queue 基本介绍
priority_queue就是优先级队列。其头文件就是queue,但是队列和优先级队列关系不大,两个是不同的数据结构。但二者都是适配器,容器适配器。
关于适配器和仿函数会在最后的时候讲。
先说基本的。
根据优先级队列的名字就可以知道其存放的数据是有优先级的。
里面就下面这几个函数:
而且常用的就empty, size, top, push, pop。
意思就不讲了,看过我前面string、vector、list、栈和队列的话,肯定是知道这些函数是干啥的。
我就直接演示一下就好:
用法几乎是与栈和队列一样。
但是这里是打印结果排好序了,降序。
所以,优先级队列默认情况下是大的优先。打印升序等会讲。
优先级队列的适配器
看其第二个模板参数:class Container = vector<T>
这就是容器适配器。
看过前一篇栈和队列的话估计你就懂什么意思了,如果不懂适配器是啥,可以看看:STL栈和队列
我们可以将其底层的容器改为其他的容器,像我们上一篇讲的deque就可以:
list是不行的,至于什么行什么不行在模拟实现的时候再说。
第三个模板参数compare
其实前面几篇中用到算法库中的sort时已经见过了,但是当时并不知道其底层是啥样的,那么这篇就能了解一下了,但这里就还是先说咋用,最后模拟实现的时候再说底层。
跟sort用法很像,sort第三个参数传的是一个对象,比如说给sort传greater<int>()就是降序,而这里传的是类型,比如说传greater<int>就是小堆。可以看到,模板参数缺省值为less<typename Cntainer::value_type>,可能有的同学不知道value_type是啥,其实就是我们日常放在容器中的元素类型,就是那个T,T可以为int、char什么的都行。所以我们默认情况下参数 Container 就是less<T>的类型,那么就是大堆。
给个例子:
这样就变成了小堆,每次取堆顶的值就是最小值。
这里要记住:
传greater时是小堆。
传less时是大堆。
这样基本用法就算讲好了,不要说这点怎么够,就几个函数,没什么好讲的,重点在后面。
带大家来做一道题。
题目链接:数组中的第K个最大元素
题目中有要求,必须设计时间复杂度为O(N)的算法。
那么想到排序的同学就另寻他路吧。
这道题就可以用到优先级队列,就是堆。
先把堆建好,然后pop k-1次后的堆顶就是第k大的元素。
代码如下:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> poq(nums.begin(), nums.end());
while(--k)
poq.pop();
return poq.top();
}
};
然后就是最重要的模拟实现了。
模拟实现priority_queue
学过堆的同学应该都知道,堆的实现的核心无非就是向上调整和向下调整。
而堆虽然逻辑结构上是二叉树,但是实际物理结构就是数组。我们用C++写,默认容器就是vector,因为随机访问数据的次数比较多。我们很多地方就可以直接复用vector中的函数接口,所以就需要自己动手写两个,一个是向上调整,一个是向下调整。
先把基本的给出:
push和pop要用到向上调整和向下调整:
记得这两个要设置为私有。
然后把基本的接口给出:
测试一下:
上面基本功能的是实现了,但是还是有点问题的,就是如何控制大小堆?
不能说别人用的时候还要把代码改一下才行,这样实现出来的东西也太拉垮了,能不能像库中那样,再搞一个模板参数,传一个仿函数来实现大小堆的控制。
肯定是可以的,但那么先说一下仿函数是什么。
仿函数
很简单,不要被名字吓到。
就是一个类,里面重载了()运算符。
看:
这就是那个less。非常的简单。
用一下:
第一个ls(1, 2)乍一看就像是函数调用,但实际上就是类对象调用了operator()。
可能看起来不是很熟悉,看这个:
这个看起来就相对熟悉一点了,sort里传参就是红色框出来的匿名对象。
再来个greater:
测试:
这就是仿函数,用类来重载()来实现。调用的时候就像函数一样,我们C语言阶段学过qsort,传比较的那个参数的时候要传函数指针,但是函数指针太麻烦了,所以C++为了不再用函数指针,就搞了仿函数。
那么此时我们就可以搞第三个模板参数了。
传的是类型。
然后把我们向上调整和向下调整中的代码改一改:
上面的priority_queue、less、greater都是在一个命名空间FangZhang中的,所以除了vector是复用的,剩下的都是手写的,less和greater就是刚写出来的那两个,就可以直接用了。
测试一下:
完全是可以用的。
再用一下库中的:
也是可以的。
再来强调一点。
假如说一个对象vector v,我们用sort时,传参是sort(v.begin(), v.end(), less<int>())
而这堆这是定义对象传模板参数priority_queue<int, vector<int>, less<int>>
前者是传匿名对象,后者是传类型。是不一样的。不要搞混。
差不多完了。
前面栈和队列还有这个优先级队列都是容器适配器,就是可以改变其底层所使用的容器,从而能够用不同的容器来实现其底层的函数接口。
下一篇讲迭代器适配器。
到此结束。。。