priority_queue的介绍
对于priority_queue顾名思义是一个名叫优先级队列的类。但是仅仅通过这个优先级我们并不能判断其具体的用法以及功能。但是如果我们说到优先级队列的本质上应该是一个堆呢?
我们的优先级队列实质上是使用堆进行实现的,其功能就是对我们插入的数据进行构建,构建成为一个堆的形式。堆顶的元素永远是数组数据当中最大或者最小的,因此我们可以通过和最后一个数据的交换就可以实现对数组的排序。
那么我们就不难想到我们优先级队列应当拥有的函数接口:首先我们需要拥有一个数据的插入接口push,以及一个数据的删除接口pop。还应该有一个获得堆顶元素的函数top,这三个是我们优先级队列当中的主要的功能。实质上优先级队列的功能也就这么几个。成员函数如下:
priority_queue的底层实现
首先我们先来观察以下priority_queue的模板参数的构造形式:对于我们的优先级队列来说有三个模板参数,第一个模板参数是对象class当中存储的数据的内省,第二个模板参数是指priority_queue实现的底层结构容器类型,第三个参数似乎我们之前并没有讲过,但是通过英文的翻译我们可以猜出应该是一个比较之类的功能,之后我们会慢慢讲解,我们先实现拥有前两个参数模板的优先级队列。
首先我们需要创建好一个模板类的结构:
也就是创建一个拥有两个模板参数的模板类,之后使用container作为成员变量的类型创建一个成员变量即可。下一步就是实现相应的函数功能了。
通过阅读相关文档我们可以发现,优先级队列当中的函数功能实质上跟我们的栈的函数功能基本相同。
push函数接口
首先我们来实现priority_queue的push函数接口,我们需要向数组当中插入一个数据,但是我们又必须保证构成我们优先级队列底层的堆的结构保持不变。所以对于插入函数来说,我们实际上需要进行的也就是对堆的数据插入操作以及向上进行调整的操作。
之后进行测试我们代码的逻辑是否可以正常运行,即插入一组数据判断是否可以构成堆的结构。
我们的逻辑是构建一个小堆的结构,实质上我们数组当中的数据也被我们修改成为了小堆的结构,代码运行一切正常。
pop函数
当我们删除数据的时候,我们需要先将堆顶的元素和堆底元素进行交换,之后删除该元素。并从堆顶开始向下进行调整,同样保证堆的结构。
程序运行结果如下:
我们会发现程序运行一切正常。
之后完成相关部分的函数功能即可。
模板参数compare
但是大家会发现我们这个优先级队列只能进行从小到大的排序。那么如果我们想要实现从大到小进行排序怎么办呢?这个时候我们就需要用到我们第三个模板参数了。
第三个模板参数compare的作用就是执行函数当中比较的逻辑,通过手动传入的形式进行控制构建大堆或者小堆。但是在实现该功能的时候,我们需要先学习一个概念:仿函数。
仿函数
所谓的仿函数实际上就是我们的类,在对()进行运算符重载操作。之后通过运算符重载功能使得我们实例化出的对象可以实现类似于函数的用法。
我们可以对()进行重载之后返回一个bool类型的返回值,作为我们比较大小的结果。使用方式如下:
首先创建一个私有的成员变量,之后通过该成员变量调用()运算符重载,直接进行数据之间的比较效果。我们会发现_com对象在使用的时候就像一个函数一样,所以叫做仿函数。
我们只需要将priority_queue当中数据的比较语句修改成为使用compare进行比较即可。代码测试效果如下:
代码运行一切正常。之后尝试通过传入不同的参数进行从小到大进行比较排序。
代码运行一切正常。
reverse_iterator
之后我们来补充之前欠下的一个“坑”——反向迭代器。相信大家或多或少都见过反向迭代器,但是我们之前并没有实现该功能,在这里我们就来完整我们的逻辑结构。
实质上我们的反向迭代器跟正向迭代器相同,都是为了实现相应的功能重新构建出来的一个类。同时由于我们的很多函数当中都有反向迭代器的需求,所以我们还可以将该类设置成为模板类的形式,提供指定的接口供多个类进行实例化使用。
我们可以向反向迭代器当中传入一个正向迭代器,我们可以通过该正向迭代器实现我们反向迭代器的功能。实质上的功能和我们正向迭代器的主要功能相同。
主要的功能如上图所示:对于我们的反向迭代器来说需要从最后一个位置开始进行从后向前进行数据的遍历操作。为了跟我们的正向迭代器相吻合所以我们需要将反向迭代器的开始设置成为正向迭代器的结束,将反向迭代器的结束设置成为正向迭代器的开始。如下图所示:
所以我们需要在解引用操作进行特别的注意:实际上我们对当前位置解引用需要进行的操作是对对应的正向迭代器的前一个位置进行解引用操作。
其他的依据正常的逻辑执行相应的操作即可,比如我们反向迭代器的++操作需要调用正向迭代器当中的--操作,--操作需要调用正向迭代器当中的++操作等。
在我们实现完成相关的函数之后就可以进行相应的使用,但是使用之前我们需要注意补充好自定义类当中的反向迭代器的函数rbegin和rend即可。
运行相关的代码如下:
阅读相关逻辑发现代码运行一切正常。