目录
0. 引言
1. priority_queue 介绍
1.1 构造函数
1.2 priority_queue 接口函数使用
1.3 仿函数
1.4 题目练习
2. priority_queue 模拟实现
2.1基本框架:
2.2 默认构造函数
2.3 基本函数
2.4 堆的向上以及向下调整
0. 引言
优先队列 (priority_queue) 是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。优先队列和堆本质是一样的,以数组形式存储的完全二叉树。
1. priority_queue 介绍
1.1 构造函数
我们可以看到有两种构造方式,一个是构造一个空对象,另一个是通过迭代器的区间来构造,默认的构造出的是大堆。
priority_queue<int> pq1; //直接构造空对象
接下来我们分别以大堆和小堆的方式来构造对象:
vector<int> v1 = {3,2,7,6,0,4,1,9,8,5};priority_queue<int, vector<int>, less<int>> pq1(v1.begin(), v1.end());//less-大堆while (!pq1.empty()){cout << pq1.top() << " ";pq1.pop();}cout << endl;priority_queue<int, vector<int>, greater<int>> pq2(v1.begin(), v1.end());//greater-小堆while (!pq2.empty()){cout << pq2.top() << " ";pq2.pop();}cout << endl;priority_queue<int> pq3(v1.begin(), v1.end());//默认大堆while (!pq3.empty()){cout << pq3.top() << " ";pq3.pop();}cout << endl;
因此我们得出: less - 大堆, greater - 小堆。
1.2 priority_queue 接口函数使用
接口函数主要包括以下:
函数 | 说明 |
empty | 检测优先级队列是否为空,是返回true,否则返回 false |
top | 返回优先级队列中最大(最小元素),即堆顶元 |
push | 在优先级队列中插入元素x |
pop | 删除优先级队列中最大(最小)元素,即堆顶元素 |
1.3 仿函数
仿函数又名函数对象 function objects 仿函数的主要作用是借助类和运算符重载,做到同一格式兼容所有函数。由于模板将 less 用作大堆,而 greater 用做小堆,是在有点别扭,如果是我们自己实现仿函数的化,肯定会按照习惯来写,less 表示小堆,greater 表示大堆。例如:
template<class T>
struct less
{bool operator()(const T& x, const T& y){return x < y;}
};template<class T>
struct greater
{bool operator()(const T& x, const T& y){return x > y;}
};
1.4 题目练习
优先级队列适合来进行TOPK 以及 排序问题,因为其底层是和堆一模一样的。现在我们一起来看下面这道题:
这题如果不关心时间复杂度,直接利用 sort 排序将会很简单:
class Solution {
public:int findKthLargest(vector<int>& nums, int k) {sort(nums.begin(),nums.end());return nums[nums.size()-k]; }
};
当我们使用优先级队列时,时间复杂度会更好:
//大堆
class Solution {
public:int findKthLargest(vector<int>& nums, int k) {priority_queue<int> pq1(nums.begin(), nums.end());for(int i = 0;i < k-1; i++){pq1.pop();}return pq1.top(); }
};
//小堆
class Solution {
public:int findKthLargest(vector<int>& nums, int k) {priority_queue<int,vector<int>, greater<int>> pq1(nums.begin(), nums.begin()+k);for(int i = k ;i < nums.size(); i++){if(nums[i] > pq1.top()){pq1.pop();pq1.push(nums[i]);}}return pq1.top(); }
};
2. priority_queue 模拟实现
优先级队列的模拟实现,难点在于堆的向上和向下调整。我们首先展现整体代码,然后再逐一分进行分析。
2.1 整体代码
我们单独创建了一个 PriorityQueue.h 文件。
#pragma once
#include<vector>
#include<iostream>
using namespace std;
namespace LHY
{template<class T, class Container = vector<T>>class priority_queue{public:void adjust_up(int child){int parent = (child - 1) / 2;while (child > 0){if (_con[parent] < _con[child]){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(int parent){size_t child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size()&& _con[child] < _con[child + 1]){++child;}if (_con[parent] < _con[child]){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}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);}const T& top(){return _con[0];}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};
从中我们可以看到私有成员变量为底层容器对象。即 Container _con; class Container = vector<T> 。
2.2 基本函数
基本函数为:
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);
}const T& top()
{return _con[0];
}size_t size()
{return _con.size();
}bool empty()
{return _con.empty();
}
我们可以清楚的看到,优先级队列的插入和删除,返回大小以及判空直接调用底层容器接口即可。而因为需要满足堆的性质,push 和 pop 则需要向上和向下调整堆。此处我们默认建立的是大堆,当push一个数比其父节点大,则需要交换位置,即向上调整。当 pop 操作则是先首尾交换,再删除尾元素,此时不满足堆条件,顶部元素则需要向下调整。如下图所示:
2.3 模拟实现
void test_priority_queue()
{priority_queue<int> pq;pq.push(1);pq.push(0);pq.push(5);pq.push(2);pq.push(1);pq.push(7);while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;
}
2.4 仿函数切换大小堆
我们在 1.3 介绍了仿函数及其实现,那么我们能不能应用到上面的代码中呢?答案是当然可以。
代码如下:
namespace LHY
{template<class T>struct less{bool operator()(const T& x, const T& y){return x < y;}};template<class T>struct greater{bool operator()(const T& x, const T& y){return x > y;}};template<class T, class Container = vector<T>,class Comapre = less<T>>class priority_queue{public:void adjust_up(int child){Comapre com;int parent = (child - 1) / 2;while (child > 0){//if (_con[parent] < _con[child])if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(int parent){size_t child = parent * 2 + 1;Comapre com;while (child < _con.size()){/*if (child + 1 < _con.size()&& _con[child] < _con[child + 1])*/if (child + 1 < _con.size()&& com(_con[child], _con[child + 1])){++child;}/*if (_con[parent] < _con[child])*/if (com(_con[parent], _con[child])){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}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);}const T& top(){return _con[0];}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};
template<class T, class Container = vector<T>,class Comapre = less<T>>
if (com(_con[parent], _con[child]))等等的变化使得我们可以改变大小堆。例如:
2.5 日期类
假设我们现在的数据不是 vector<int> 而是 日期类对象,会发生什么情况呢?我们先看日期类代码:
class Date{public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}friend ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "-" << d._month << "-" << d._day;return _cout;}private:int _year;int _month;int _day;};class PDateLess{public:bool operator()(const Date* p1, const Date* p2){return *p1 < *p2;}};class PDateGreater{public:bool operator()(const Date* p1, const Date* p2){return *p1 > *p2;}};
这时候我们创建数据为Date 的优先级队列(大堆),取堆顶元素(判断是否能对自定义类型进行正确调整)
此时没有问题, 因为在实际比较时,调用的是 Date 自己的比较逻辑。
那么如果是Date* 呢?我们再来看:
我们发现多次运行结果不一样 ,因为此时调用的是指针的比较逻辑(地址是随机的,因此结果也是随机的)。
那么,我们要如何去解放呢?可以专门创建一个为Date* 比较的仿函数。
class PDateLess{public:bool operator()(const Date* p1, const Date* p2){return *p1 < *p2;}};class PDateGreater{public:bool operator()(const Date* p1, const Date* p2){return *p1 > *p2;}};
这样便没有问题了: