文章目录
一、堆以及建堆函数
优先队列的核心思想之一就是堆排。但是注意!堆可不是堆栈的堆!
【定义】堆,其实是用vector组成的完全树(因为完全树再层序遍历的时候就是用向量也可以完美表现)。其思想就是先构建出堆,然后会出现一个顶点一定为(最大或最小)的偏序。然后每次把顶点拿走之后再下滤即可。
【步骤】首先根据性质定义好构造建堆下数据结构中优先队列的性质,里面是完全二叉树的性质)
下滤函数–主要使用递归(用于下文的建堆和删顶)
因为主要用递归,所以要记住其需要的三个参数:加&的vector,这个里面放着建堆用的数据。i表示父节点,也是要将左右两个子树合并起来的关键。最后一个i合并建堆就结束了。heapsize是为了确保过程中,不要过了vector的界(其实这个size一直就是vector的size)。
1、先根据性质找到左右子节点的位置:由于父节点为
i
i
i
左子节点为:
2
i
+
1
2i+1
2i+1右子节点:
2
i
+
2
2i+2
2i+2。
2、父节点将与左右子节点大于它并且更大的那个进行互换
(注意此处有两个条件,不仅要大于父节点,他俩还只能取一次,此时技巧来了!)
2.2定义一个large =
i
i
i,然后large会分别与左右节点比较,并只要大于它,large就等于他。这样两个if过后,它就是最大的那个了。
2.3上一小步中定义两个if,一个条件是左右别超界爆炸,另一条件判断左右在vector中是否大于父节点,如都不大直接别判断了。
2.4 此时如果large不是i,即存在有比他大的子节点了,就直接swap一下,然后接着进行递归。即下一个继续传入a,但是父节点就是large了,其中size一直不变
【ps】难道large为i就不递归了吗,还真就是这样,不递归了!
记忆代码:
void low_update(vector<int>& nums, int i,int heap_size) {
int left = 2*i+1;int right = 2*i+2;int large = i;
if(left < heap_size && nums[left]>nums[large]) large = left;//由此看出是升序
if(right < heap_size && nums[right]>nums[large]) large = right;
if(large!=i)
{
swap(nums[i],nums[large]);
low_update(nums, large, heap_size);
}
}
建堆函数
【思想】根据floyd算法,自下而上的下滤,从下面两两合并最终合成一个完整树。
其实就是从下向上的拿vector倒序建,建完整体没顺序,但是只要符合操作上下滤,顶点就一直是极值。就一个for循环,从后往前传一半的i就行了!
因此for(int i = n/2;i>=0;i–)!!!其他没孩子无所谓了。
void build_heap(vector<int>& nums,int n) {
for(int i = n/2;i>=0;i--)
{
low_update(nums,i,n);
}
}
三、删顶(取一次删一次,取几次删剩下的就是要求的了)
注意是涉及到上滤的
1、先建堆–此后vector可就是堆的形状了!
2.1、pop出去【其实仔细想想不知道如何pop呀】但是联系下一步有个妙招
2.2、头跟尾巴交换位置,再下滤
3、将两个第二步融合一下,就是将头和尾巴交换位置,然后size-1之后,放入下滤就ok了!
最后,返回头,也就是nums[0]
二、调用在stl库里面的优先队列priority_queue
首先,需要引入库#include <queue>
。下面将分别根据建堆所需的数据类型情况进行分类描述:
首先,大根堆就是每次取最大值,小根堆就是每次取最小值。但是,对于最大和最小堆栈在定义排序顺序时,是与sort中的逻辑正好相反的。(如在下面情况1中,greater<int>
对应的是最小堆,而在sort中的greater<int>()
对应的是从大到小排!原因可见情况3讲解)
建堆
1、单个数的情况
//小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//大顶堆
priority_queue <int,vector<int>,less<int> >q;
//默认大顶堆
priority_queue<int> a;
里面可以是正常int,但更多的是pair,会对前面的进行排序,取后面的就可以!
2、两个数pair的情况
pair的比较,先比较第一个元素,第一个相等比较第二个。
//小顶堆
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> q;
//大顶堆
priority_queue<pair<int, int>, vector<pair<int, int>>, less<>> q;
//默认大顶堆
priority_queue<pair<int, int>, vector<pair<int, int>>> q;
当pair想比较第二个的时候,可以选择前后切换即可。
3、自定义类型的情况
若要使相对树的节点等不是Int类型的使用优先队列,则可以通过重定义操作符"<",以链表的节点为例,可以这样声明优先队列。
struct Status {
int val;
ListNode *ptr;
bool operator < (const Status &rhs) const {
return val > rhs.val;
}//注意这个是最小堆,最小堆是>;最大堆是<,两个堆都需要重定义<
};
priority_queue <Status> q//这样就能直接声明定义了
由于堆原理中,默认是下面的左右子节点大于上面的父节点,便让父节点跟最大的那个换一下,而此处修改便是找最大的那个条件,即该结构中源代码是<,而编写的新仿函数或者重载<,使其return 大于的才对,就是将<变成了>。因此就会从默认的由小到大排转成了由大到小排!
4、仅仅想重构“比较规则”的情况
可以使用重构()函数的方法(其逻辑比较顺序与上面的重构<函数一模一样):
struct cmp1
{
bool operator()(char &a,char &b) { return a < b; }//这是大根堆
//注意这里定义的逻辑跟sort定义是反着的
};
struct cmp2
{
bool operator()(pair<double,vector<int>>&p1,pair<double,vector<int>>&p2)
{
return p1.first > p2.first;//小顶堆是大于号
}
};
priority_queue<char,vector<char>,cmp1> a;//例如a中有'a'和'b',此时top出来的是b
priority_queue<pair<double,vector<int>>,vector<pair<double,vector<int>>>,cmp2> b;
5、想通过sort那样使用匿名函数快速定义排序顺序
在 priority_queue 的构造函数中,可以传入一个比较函数作为参数,用于指定元素的优先级比较方式。
因此我们可以先声明一个匿名函数,随后使用decltype
函数来使该函数变为一个type
从而使用。
auto cmp = [&](const int& a, const int &b) {
return cnt[a] < cnt[b];//此处cnt可由上文完成定义(最大堆--跟sort正好相反)
};
priority_queue<int, vector<int>, decltype(cmp)>pq{cmp};
堆操作
以上面堆栈a
为例:
加入堆节点:a.push('a');
删除堆顶节点:a.pop();
取出堆顶节点:a.top();