【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现

优先级队列

    • 前言
    • 正式开始
      • priority_queue 基本介绍
      • 优先级队列的适配器
      • 第三个模板参数compare
      • 模拟实现priority_queue
        • 仿函数

【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第1张图片

前言

点进来的小伙伴不知道学过数据结构里的堆没有,如果学过的话,那就好说了,优先级队列就是堆,如果没学过,没关系,可以参考一下我之前写的一篇关于堆的博客,可以点进去看看:【数据结构】堆(包含堆排序和TOPK问题)

那么了解过堆了的话,我就不讲那么细致了,就把STL中给的接口说一遍,然后模拟实现一下,再说一下适配器就好了。

正式开始

还是cpluplus这个网站:queue

【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第2张图片

priority_queue 基本介绍

priority_queue就是优先级队列。其头文件就是queue,但是队列和优先级队列关系不大,两个是不同的数据结构。但二者都是适配器,容器适配器。

【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第3张图片
关于适配器和仿函数会在最后的时候讲。

先说基本的。

根据优先级队列的名字就可以知道其存放的数据是有优先级的。
里面就下面这几个函数:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第4张图片
而且常用的就empty, size, top, push, pop。

意思就不讲了,看过我前面string、vector、list、栈和队列的话,肯定是知道这些函数是干啥的。

我就直接演示一下就好:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第5张图片
用法几乎是与栈和队列一样。
但是这里是打印结果排好序了,降序。

所以,优先级队列默认情况下是大的优先。打印升序等会讲。

优先级队列的适配器

【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第6张图片

看其第二个模板参数:class Container = vector
这就是容器适配器。

看过前一篇栈和队列的话估计你就懂什么意思了,如果不懂适配器是啥,可以看看:STL栈和队列

我们可以将其底层的容器改为其他的容器,像我们上一篇讲的deque就可以:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第7张图片
list是不行的,至于什么行什么不行在模拟实现的时候再说。

第三个模板参数compare

【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第8张图片

其实前面几篇中用到算法库中的sort时已经见过了,但是当时并不知道其底层是啥样的,那么这篇就能了解一下了,但这里就还是先说咋用,最后模拟实现的时候再说底层。

跟sort用法很像,sort第三个参数传的是一个对象,比如说给sort传greater()就是降序,而这里传的是类型,比如说传greater就是小堆。可以看到,模板参数缺省值为less,可能有的同学不知道value_type是啥,其实就是我们日常放在容器中的元素类型,就是那个T,T可以为int、char什么的都行。所以我们默认情况下参数 Container 就是less的类型,那么就是大堆。

给个例子:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第9张图片
这样就变成了小堆,每次取堆顶的值就是最小值。

这里要记住:
传greater时是小堆。
传less时是大堆。

这样基本用法就算讲好了,不要说这点怎么够,就几个函数,没什么好讲的,重点在后面。

带大家来做一道题。
题目链接:数组中的第K个最大元素

【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第10张图片
题目中有要求,必须设计时间复杂度为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中的函数接口,所以就需要自己动手写两个,一个是向上调整,一个是向下调整。

先把基本的给出:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第11张图片

push和pop要用到向上调整和向下调整:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第12张图片
记得这两个要设置为私有。

然后把基本的接口给出:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第13张图片

测试一下:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第14张图片

上面基本功能的是实现了,但是还是有点问题的,就是如何控制大小堆?
不能说别人用的时候还要把代码改一下才行,这样实现出来的东西也太拉垮了,能不能像库中那样,再搞一个模板参数,传一个仿函数来实现大小堆的控制。

肯定是可以的,但那么先说一下仿函数是什么。

仿函数

很简单,不要被名字吓到。

就是一个类,里面重载了()运算符。

看:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第15张图片
这就是那个less。非常的简单。

用一下:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第16张图片
第一个ls(1, 2)乍一看就像是函数调用,但实际上就是类对象调用了operator()。

可能看起来不是很熟悉,看这个:
在这里插入图片描述
这个看起来就相对熟悉一点了,sort里传参就是红色框出来的匿名对象。

再来个greater:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第17张图片
测试:

【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第18张图片

这就是仿函数,用类来重载()来实现。调用的时候就像函数一样,我们C语言阶段学过qsort,传比较的那个参数的时候要传函数指针,但是函数指针太麻烦了,所以C++为了不再用函数指针,就搞了仿函数。

那么此时我们就可以搞第三个模板参数了。
在这里插入图片描述
传的是类型。
然后把我们向上调整和向下调整中的代码改一改:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第19张图片
上面的priority_queue、less、greater都是在一个命名空间FangZhang中的,所以除了vector是复用的,剩下的都是手写的,less和greater就是刚写出来的那两个,就可以直接用了。

测试一下:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第20张图片
完全是可以用的。
再用一下库中的:
【C++】STL优先级队列(priority_queue)功能介绍以及模拟实现_第21张图片
也是可以的。

再来强调一点。

假如说一个对象vector v,我们用sort时,传参是sort(v.begin(), v.end(), less())
而这堆这是定义对象传模板参数priority_queue, less>
前者是传匿名对象,后者是传类型。是不一样的。不要搞混。

差不多完了。
前面栈和队列还有这个优先级队列都是容器适配器,就是可以改变其底层所使用的容器,从而能够用不同的容器来实现其底层的函数接口。

下一篇讲迭代器适配器。

到此结束。。。

你可能感兴趣的:(c++,开发语言,算法,priority_queue,stl)