大家好!这篇文章,主要讲解一下这个优先级队列,还包含了仿函数等等的知识。希望大家能够一起加油!!!
文章目录
- 1. priority_queue的实现
- 1.1 push函数
- 1.2 pop函数
- 1.3 top函数和empty函数
- 2. 仿函数
- 2.1 仿函数的由来和定义
- 2.2 改造priority_queue
- 2.3 sort的仿函数
- 2.4 什么时候要写仿函数呢
- 3. priority_queue的构造
1. priority_queue的实现
它在STL中是这个样子:
我们先不看仿函数,先把框架实现一下:
它的成员函数有这些,我们一个一个来实现。
1.1 push函数
我们这里把数据插入到容器里了,但还是不行。因为它还不是一个大堆。我们需要写一个向上调整来创建一个大堆。大堆是如何创建的呢?不懂的可以看这里:堆的创建
实现如下:
这样,每当我们插入一个数据时,它会向上调整,形成一个大堆。
1.2 pop函数
pop函数其实是删除堆的顶部元素。那么堆的删除是如何做的,我们可以看这里:堆的删除
实现如下:
1.3 top函数和empty函数
这里使用引用可能里面存的是像string这样的类,拷贝构造的代码比较大。加const是因为我们不能改变里面的元素,防止它不是一个堆了。
这两个函数比较简单,就不多说了。
我们来测试一下写的对不对:
我们可以看到是没有什么太大的问题。这里我们给它写死了,如果我们想升序排列,就没有办法了。这时,我们需要用仿函数的知识。
2. 仿函数
2.1 仿函数的由来和定义
我们知道,在C语言中我们使用函数指针的时候会很麻烦。而仿函数就是解决这个问题。
定义:仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。
举个例子:
我们在这里写了一个less类,里面重载了()运算符。所以我们就可以这样去使用:
这里我们用这个less类来定义一个对象,然后用这个对象来调用重载的()运算符。从这里我们可以看到它的调用方式和函数非常的相似。这样我们也可以来比较大小。如果我们想比较不同类型的,我们可以加上模板。
有比小的,也会有比较大的。
那么这样的话,我们就可以把这个设成我们priority_queue的模板参数。当传不同的仿函数时,可以调用不同的方法。
2.2 改造priority_queue
然后我们可以这样来写:
这样默认的情况下,传的是less这个类模板,父亲比孩子小交换,也就是创建的是大堆。如果我们给它传greater类模板,父亲比孩子大交换,创建的就是小堆。
向下调整也是一样的道理。现在我们就来测试一下这样写行不行:
默认情况下是大堆,没有问题。
小堆也没有问题。
2.3 sort的仿函数
它和priority_queue的区别在priority_queue是在模板中传类型,而sort是在函数中传递这个对象。
举个例子:
其实在这里,我们也不是说必须要传迭代器才能排序。我们也可以传普通的数组。
因为指向数组的原生指针,本身就是天然的迭代器。
2.4 什么时候要写仿函数呢
看下面的例子:
对于自定义类型,我们把它们进行比较,不能直接比,我们需要自己写比较函数。
这里我们用的是以价格默认升序排序,但是如果我们现在想以销售的数量为升序排,该怎么办呢?它就不能灵活的排了,此时我们就需要写仿函数。
这样,我们就可以灵活的去比较自定义类型的各个成员变量了。
3. priority_queue的构造
我们来看一下库里的构造是如何写的:
我们先看第一个。其实在C++里,我们需要兼容C语言,也就是需要兼容函数指针。
举个例子:
这里我们写了一个函数,如果用C语言的话,我们需要用函数指针。因为priority_queue传的是类型。所以应该这样:
那么这里模板的话:
我们只知道指针的类型,不知道指针指向谁?只是定义了一个野指针,无法使用。所以在构造的时候,我们加了一个这样东西:
怎么写的呢?我们来看:
我们在这里加了一个成员变量。如果传的是less这种类模板,那么这个成员变量就会拷贝成这个对象,然后去调用operator()。如果传的是一个函数指针,也会定义一个对象。在C++中对内置类型升级了。
如果传的是函数指针,初始化后是一个空指针,还是不能使用。所以我们在使用的时候,还要传。
这样在初始化时就知道函数指针指向的是谁了。
然后,我们再来谈一下第二个构造:
在一些TOP-K问题可以使用。