一、介绍
优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。(注意: 默认情况下priority_queue是大堆。)
二、基本接口
函数声明 | 接口说明 |
priority queue() | 构造一个空的优先级队列 |
priority queue(first,last) | 用迭代器构造 |
empty() | 判空 |
top() | 返回堆顶元素 |
push(x) | 插入元素x |
pop() | 删除堆顶元素 |
三、模拟实现
1.成员变量
成员变量采用适配器模式,默认给的是vector
template<class T,class Con = vector<T>,class Comapre = less<T>>
class heap
{private:Con _con;...
}
2.向上调整adjust_up 与向下调整adjust_down
优先级队列最核心的两个实现就是向上调整和向下调整,是实现堆算法的核心
adjust_up
void adjust_up(size_t child)
{size_t parent = (child - 1) / 2;while (child > 0){if (com(_con[parent] , _con[child]))//com是伪函数对象,默认是小于{swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
adjust_down
向下调整的实现需要考虑只有左孩子没有右孩子和两个孩子都有这两种情况,画图判断边界条件
//com是仿函数,默认为小于void adjust_down(size_t parent){size_t lchild = parent * 2 + 1;size_t rchild = lchild + 1;while (lchild < _con.size())//没有孩子就结束向下调整{if (lchild + 1 == _con.size())//有一个左孩子{if (com(_con[parent] , _con[lchild])){swap(_con[parent], _con[lchild]);parent = lchild;}else{break;}}else//存在右孩子{size_t child = com(_con[rchild] , _con[lchild]) ? lchild : rchild;if (com(_con[parent] , _con[lchild])){swap(_con[parent], _con[child]);}else{break;}parent = child;//更新}//更新孩子lchild = parent * 2 + 1;rchild = lchild + 1;}}
3.构造函数
构造函数又两个接口,一个是构造一个空的优先级队列,一个是用迭代器进行构造
用迭代器构造的实现中,涉及建堆算法,即从最后一个节点的父节点开始向下调整,一直调整到根,就可以实现建堆,时间复杂度为O(n)
heap():_con(){}template<class Iterator>heap(Iterator first, Iterator last):_con(first,last){//建堆算法for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i){adjust_down(i);}}
4.push 和 pop
push:将数据尾插后,不断向上调整,使其重新成为一个堆
pop:将堆顶的元素与堆尾的最后一个元素交换,然后尾删,对堆顶刚交换的元素进行向下调整
void push(const T& x){_con.push_back(x);adjust_up(_con.size()-1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}
5.基本参数
注意!在返回top时,只需要返回const修饰的堆顶,堆顶一般不允许进行修改,不然会破坏堆
size_t size(){return _con.size();}bool empty(){return _con.empty();}const T& top()const{return _con.front();}
四、本章重点(新知识)—— 仿函数
伪函数的本质是类,在本章的模拟实现中,用仿函数去操控堆是大堆还是小堆,实际上就是用类去重载了(),实现类型于比较函数的功能,用实例化的对象去调用这个重载,这样做的好处是在传参的时候,可以通过模板参数去传,相比于回调函数用处更大更方便,并且开放性很强,可以自己实现一个仿函数去传,也可以使用库里的,本次模拟实现中,只是使用仿函数的其中一个情景
template<class T>
struct greater
{bool operator()(const T& x,const T& y){return x > y;}
}
template<class T>
struct less
{bool operator()(const T& x,const T& y){return x < y;}
}//类模板 : template<class T , class Con = vector<T> , class Comapre = less<T>>
五、相关的经典OJ题
1.topK问题
题目链接
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述
找到一组数据中,前k个或者第k个(排序后)的数据
解题思路
直接用堆去解决,在本题目中,最好的方法是利用快排的思想,才能实现O(n)的时间复杂度
参考代码
class Solution
{
public:int findKthLargest(vector<int>& nums, int k) {priority_queue<int> hp(nums.begin(),nums.end());while(--k){hp.pop();}return hp.top();}
};
总结:
本章整理了关于优先级队列的相关知识,其中比较重要的一个概念是仿函数的概念,属于现阶段的新知识点,这里简单的提一下,今后会学习更多相关的知识