一.堆的性质

1.堆是一颗完全二叉树

2.堆的顶端一定是“最大”,最小”的,但是要注意一个点,这里的大和小并不是传统意义下的大和小,它是相对于优先级而言的,当然你也可以把优先级定为传统意义下的大小,但一定要牢记这一点,初学者容易把堆的“大小”直接定义为传统意义下的大小,某些题就不是按数字的大小为优先级来进行堆的操作的

(但是为了讲解方便,下文直接把堆的优先级定为传统意义下的大小,所以上面跟没讲有什么区别?

3.堆一般有两种样子,小根堆和大根堆,分别对应第二个性质中的“堆顶最大”“堆顶最小”,对于大根堆而言,任何一个非根节点,它的优先级都小于堆顶,对于小根堆而言,任何一个非根节点,它的优先级都大于堆顶(这里的根就是堆顶啦qwq)

二. 堆的STL实现

这年头真的没几个人写手写堆~~(可能有情怀党?)~~

一是手写堆容易写错代码又多,二是STL 直接给我们提供了一个实现堆的简单方式:优先队列

手写堆和STL的优先队列有什么 区别?没有区别

速度方面,手写堆会偏快一点,但是如果开了O2优化优先队列可能会更快;

代码实现难度方面:优先队列完爆手写堆

这两方面综合起来,一般都是用STL的优先队列来实现堆,省选开O2啊

至于为什么前面讲堆的操作时用手写堆,好理解嘛,最好先根据上面的代码和图理解一下堆是怎么实现那些操作的,再来看一下下面的STL的操作

定义一个优先队列:

首先你需要一个头文件:#include<queue>
priority_queue<int> q;//这是一个大根堆q
priority_queue<int,vector<int>,greater<int> >q;//这是一个小根堆q
//注意某些编译器在定义一个小根堆的时候greater<int>和后面的>要隔一个空格,不然会被编译器识别成位运算符号>>

优先队列的操作

q.top()//取得堆顶元素,并不会弹出
q.pop()//弹出堆顶元素
q.push()//往堆里面插入一个元素
q.empty()//查询堆是否为空,为空则返回1否则返回0
q.size()//查询堆内元素数量

常用也就这些,貌似还有其他,不过基本也用不到,知道上面那几个也就可以了

不过有个小问题就是STL只支持删除堆顶,而不支持删除其他元素

但是问题不大,开一个数组del,在要删除其他元素的时候直接就标记一下del[i]=1,这里的下标是元素的值,然后在查询的时候碰到这个元素被标记了直接弹出然后继续查询就可以了 (前两天刚从学长处get这个姿势)

另外因为STL好写,下面堆的应用全部都会采用STL的代码实现~~(懒啊,如果有放代码的话)~~

这里补一下重载运算符在STL的优先队列中应用到的知识

重载运算符是什么?

把一种运算符变成另外一种运算符(注意,都必须是原有的运算符),比如把<号重载成>号,这个东西学过STL中的sort的同学应该会比较熟悉

这个在优先队列中有什么用处呢?

之前我们就讲到了,大根堆,小根堆的“大”和“小”都不是传统意义下的“大”和“小”,重载运算符在STL的优先队列中就是用来解决这种“非传统意义的‘大’和‘小’”的

现在你有一个数列,它有权值和优先级两种属性,权值即该数的大小,优先级是给定的,现在要你按照优先级的大小从小到大输出这个数列

这不是Treap吗?这不是sort吗?

以上两个东西都可以用来实现这道题(逃,而且就实用性而言,sort用来解决这道题是最方便的,但是我们现在要讲的做法是使用堆排序的方式来解决这道题(堆排序是什么?下文堆的应用中有提到)

首先应该想得到结构体,我们定义一个结构体

struct node{
    int val,rnd;
}a[100];

但是使用传统做法是行不通的,在小根堆中是通过比较数的大小来确定各个元素在堆中的位置的,但是对于这个a数组,你是要对比权值val的值,还是要对比优先级rnd的值?

这时候重载运算符就派上用场了

我们在结构体里面再加3行东西

struct node{
    int val,rnd;
    bool operator < (const node&x) const {
        return rnd<x.rnd;
    }
}a[100];

这个玩意为什么要这么写呢?

首先这个玩意是bool类型的,因为你只需要判断这两个是大,还是小;然后,要重载运算符就必须加一个operator这个玩意,不然计算机怎么知道你要干嘛?后面接一个你要重载的运算符,这里是“<”,再后面的括号里面的东西则是你要比较的数据类型,这里是数据类型为node,并且加了一个指针&,将对这个x的修改同步到你实际上要修改的数据那里。然后就是记得加那两个const

然后两个大括号里面就是你重载的内容了,这里是把比较数的大小的小于号,重载成比较node这个数据类型里面的优先级的大小

这个玩意讲的比较多,主要是因为是一个很难懂的东西~~(对我来说?反正当时学的时候就是感觉很晦涩难懂,这里就尽量写详细一点,给和当初的我一样的萌新看一下)~~

而且在实际中,这个东西的用处也很大,就说在堆里面的应用,在NOIP提高,省选的那个级别,就绝对不可能考裸的堆的,往往你要比较的东西就不是数的大小了,而是按照题目要求灵活更改,这时候重载运算符就帮得上很大忙了

这也就是为什么我在前面反复强调,堆里面的大小,并非传统意义下的大小

三.堆的应用

1.堆排序

其实就是用要排序的元素建一个堆(视情况而定是大根堆还是小根堆),然后依次弹出堆顶元素,最后得到的就是排序后的结果了

但是裸的并没有什么用,我们有sort而且sort还比堆排快,所以堆排一般都没有这种模板题,一般是利用堆排的思想,然后来搞一些奇奇怪怪的操作,第2个应用就有涉及到一点堆排的思想

2.用两个堆来维护一些查询第k小/大的操作

posted on 2023-02-21 17:16  ljq0120  阅读(32)  评论(0编辑  收藏  举报