堆的介绍
值得关注的知识点:
- 堆可以被看做一个一维数组,也可以被看作是棵完全二叉树。堆其实就是利用完全二叉树的结构来维护的一维数组。
- 堆主要用来做优先队列,因为它可以访问到最重要的元素,而在堆中搜索元素会比较慢。
- 升序排序用大顶堆,降序排序用小顶堆。以大顶堆为例,没做一次堆排序,就可以得到序列中的最大值,把它放到堆数组的最后面,则堆的末尾就是最大值了,则可以进行升序排序。(因为堆中只能保证最优的元素,不能保证整个堆都是有序的,因此要做根节点和最后一个节点的位置交换)
C++中堆的使用
1、priority_queue
C++中提供优先队列的模板供使用,优先队列的底层实现就是堆,priority_queue包含在头文件#include<queue>
中。
优先队列priority_queue也是一个队列,含有队列的一切特征,例如先进先出、只能从队头出,队尾入。优先队列在普通队列的基础上增加了内部的一个排序,这个排序本质是由堆实现的。
2、priority_queue的代码原型
template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T>> class priority_queue
可以看出,priority_queue模板有三个参数:
- 第一个参数为存储对象的类型
- 第二个参数为存储对象的底层容器,若缺省则默认为
vector
- 第三个参数为函数对象,它定义了一个用来决定元素顺序的断言,若缺省则为大根堆。这里的函数对象可以是标准库中定义好的一些比较仿函数,例如
less<T>
函数对应获得一个大顶堆,greater<T>
对应获得一个小顶堆。
3、priority_queue的初始化
创建空的priority_queue
//默认是一个使用vec作为底层容器的大顶堆
priority_queue<string>words;
使用初始值列表初始化
string words[]{"one", "two", "three"};
priority_queue<string>w{words.begin(), words.end()}; //字母顺序"two", "three", "one"
注:这里跟前面堆介绍中说升序用大根堆,降序用小根堆不一样,有可能原因是C++中优先队列的实现问题。
自定义排序函数时候的初始化
vector<int>values{25,22,54,55};
priority_queue<int>pq{less<int>(),values}; //调用pq的构造函数,其中第一个参数为比较函数对象,第二个函数用于初始化容器元素(可缺省)
4、priority_queue的函数使用
和队列基本相同:
- top 访问队头元素
- empty 队列是否为空
- size 返回队列内元素个数
- push 插入元素到队尾
- emplace 原地的构造一个元素并且插入队列
- pop
- swap
emplace可以直接调用对象的构造函数,有区别于push的参数传入的形式,属于C++中优化的方式,具体可以参考emplace与push_back
5、priority_queue的使用例子
用pair做优先级队列:先比较第一个元素,后比较第二个元素
pq.push(pair<int, int>(1, 2));
pq.push(pair<int, int>(2, 3));
pq.push(pair<int, int>(2, 4));
while (!pq.empty()) {
cout << pq.top().first << " " << pq.top().second << endl;;
pq.pop();
}
运行结果:
经典的topK问题:
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
//优先队列做法(实际上是堆排序)
map<int,int>mp;
for(auto &_num:nums){
mp[_num]++;
}
auto cmp = [](pair<int,int>a, pair<int, int>b){return a.second > b.second;}; //这里定义为小根堆
//定义优先队列,按小根堆排序,优先队列中每个pair的第一个元素为数字,第二个元素为出现次数
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)>pq{cmp}; //关注这里的定义与初始化
for(auto & [num,count] : mp){
if(pq.size() == k){
//大于堆顶元素,则需要把该元素插入堆中,并弹出堆顶元素(因为要维护一个元素数量为k的堆,固要弹出)
if(pq.top().second < count){
pq.pop();
pq.emplace(num, count);
}
}
else{
pq.emplace(num, count);
}
}
vector<int>output;
主要的思路是对元素频次维护一个含有k个元素的小根堆