@toc
优先级队列也是一种容器适配器,默认情况下它适配的是vector,以支持堆的算法中频繁的随机访问。priority_queue不像stack & queue的适配只是简单复用,还搭配了堆的算法。那么,如何控制大堆还是小堆呢?就要通过简单的仿函数啦。let's go!
正文开始
1. 优先级队列的使用
头文件:<queue>
" title="">
- Container:默认情况下,它适配的是vector。理论上,底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类,但是必须支持随机访问迭代器访问,以及一系列基本接口。
- Compare:默认情况下,大的优先级高(即默认是大堆),仿函数给的是
less
(这确实有点奇怪)。小堆需要传入仿函数类型,它的头文件在<functional>
中。
#include<iostream>
#include<queue>
#include<functional> //greater算法头文件
using namespace std;
int main()
{
//priority_queue<int> pq; //默认大的优先级高,仿函数为less
priority_queue<int,vector<int>,greater<int>> pq;
pq.push(1);
pq.push(3);
pq.push(5);
pq.push(2);
pq.push(4);
// 适配容器都不能迭代器遍历,因为这样不能保证堆的性质。
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
return 0;
}
来一道小题:215. 数组中的第K个最大元素 - 力扣(LeetCode) (leetcode-cn.com)
:purple_heart: 方法一:排序
时间复杂度:O(N*logN)
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
sort(nums.begin(),nums.end());
return nums[nums.size()-k]; //倒数第k个
}
};
或排升序
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
sort(nums.begin(),nums.end(),greater<int>());//注意,传的是匿名 对象
return nums[k-1];
}
};
:purple_heart: 方法二:建大堆(堆排) + pop (k-1)次取堆顶
时间复杂度:O(N+k*logN)
空间复杂度:O(N) :scream:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> maxHeap(nums.begin(), nums.end());
while(--k)
{
maxHeap.pop();
}
return maxHeap.top();
}
};
- 注意:迭代器区间构造,即建堆。不需要遍历push了
如果N>>k,法1和法2的代价太大,我们借鉴topK问题的思路,建一个k个数的小堆 ——
:purple_heart: 方法三:建一个k个数的小堆 + 取堆顶元素
时间复杂度:O(k+(N-k)*logk)
空间复杂度:O(k)
时间复杂度和法二相差不大,但空间消耗最小,是这道题目的极致写法。
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
//小堆
priority_queue<int, vector<int>, greater<int>> minHeap(nums.begin(), nums.begin()+k);
for(int i = k; i<nums.size(); i++)
{
if(nums[i]>minHeap.top())
{
minHeap.pop();
minHeap.push(nums[i]);
}
}
return minHeap.top();
}
};
2. priority_queue的模拟实现
priority_queue框架,它的底层是一个随机容器。模板参数的第三个参数(仿函数)后面详谈——
template<class T,class Container = vector<T>,class Compare = less<T>>
class priority_queue
{
private:
void ajust_up(size_t child);
void ajust_down(size_t parent);
public:
priority_queue()
{}
void push(const T& x);
void pop();
const T& top() const;
size_t size() const;
bool empty() const;
private:
Container _con;
};
2.1 size & empty & top
一系列简单接口。top是取堆顶元素。
const T& top() const
{
return _con[0];
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
2.2 仿函数
我们写一个类,没有任何成员变量,重载()
——
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;
}
};
不同仿函数类型的对象,用()
来调用对应的函数比较方式,就实现了像函数一样调用 ——
// Less<T> - 仿函数类型,less 仿函数对象
int main()
{
Less<int> less;
cout << less(1, 2) << endl;
cout << less.operator()(1, 2) << endl; // 等价于
cout << Less<int>()(1, 2) << endl; //匿名对象
Greater<int> greater;
cout << greater(1, 2) << endl;
cout << greater.operator()(1, 2) << endl;// 等价于
cout << Greater<int>()(1, 2) << endl; //匿名对象
return 0;
}
建大堆还是建小堆本质是由于ajust_up和ajust_down中的比较符号不同,那么就要通过仿函数来控制。
2.2.1 push & 向上调整算法
优先级队列的插入及删除,就是在堆的基础上做插入删除,堆的算法我们都相当熟悉了。
堆插 = 尾插 + 向上调整
void push(const T& x)
{
_con.push_back(x);
ajust_up(_con.size() - 1);
}
:purple_heart: 向上调整算法
" title="">
void ajust_up(size_t child)
{
while (child > 0)
{
size_t parent = (child - 1) / 2;
//if (_con[parent] < _con[child])
if (Compare()(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
}
else
{
break;
}
}
}
2.2.2 pop & 向下调整算法
堆删 = 交换 + 再删(直接头删会破坏树的关系) + 向下调整(刚好符合左右都是大/小堆的条件)
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
ajust_down(0);
}
:purple_heart: 向下调整算法
" title="">
void ajust_down(size_t parent)
{
size_t child = parent * 2 + 1;
while (child < _con.size())
{
//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
if (child + 1 < _con.size() && Compare()(_con[child], _con[child + 1]))
child++;
//if (_con[parent],<_con[child])
if (Compare()(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
向上调整和向下调整算法我们都把它实现成私有,因为不太希望别人直接调用。
2.3 构造函数
:purple_heart: 迭代器区间构造:_con自定义类型会调用它的迭代器区间构造,不用再迭代器遍历+push了。但在这基础上,还需要构建成堆,为了使左右都是大(小)堆,要倒着建,从最后一个非叶子节点(即最后一个节点的父亲)开始向下调整。
public:
priority_queue()
{}
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
: _con(first,last)
{
//构建堆结构
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
ajust_down(i);
}
}
2.4 关于T是自定义类型
我们考虑,当T是自定义类型(比如Date)时 ——
void test_priority_queue2()
{
//priority_queue<Date> pq;
priority_queue<Date, vector<Date>, greater<Date>> pq;
pq.push(Date(2022, 3, 26));
pq.push(Date(2022, 4, 1));
pq.push(Date(2022, 2, 19));
pq.push(Date(2022, 4, 10));
while (!pq.empty())
{
cout << pq.top() << endl;
pq.pop();
}
}
在仿函数中,<
和>
运算符是无法直接比较的。of course 我们可以重载运算符like this ——
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);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
但是如果数据类型 不支持比较 或 比较的方式不是你想要的,可以自己实现仿函数,按照你想要的方式(Compare给我们留的口子)去控制比较逻辑,比如 ——
void test_priority_queue3()
{
//priority_queue<Date*> pq; //默认比较地址大小
//priority_queue<Date*, vector<Date*>, lessPDate> pq;
priority_queue<Date*, vector<Date*>, greaterPDate> pq;
pq.push(new Date(2022, 3, 26));
pq.push(new Date(2022, 4, 1));
pq.push(new Date(2022, 2, 19));
pq.push(new Date(2022, 4, 10));
//默认比较地址大小,若想比较日期大小,自己写仿函数
while (!pq.empty())
{
cout << *pq.top() << endl;
pq.pop();
}
}
于是我们自己写了仿函数,又因为私有成员类外无法访问,我们把这两个仿函数类声明为priority_queue的友元类 ——
struct lessPDate
{
bool operator()(const Date* d1,const Date* d2)
{
//return *d1 < *d2;
return (d1->_year < d2->_year) ||
(d1->_year == d2->_year && d1->_month < d2->_month) ||
(d1->_year == d2->_year && d1->_month == d2->_month && d1->_day < d2->_day);
}
};
struct greaterPDate
{
bool operator()(const Date* d1, const Date* d2)
{
//return *d1 > *d2;
return (d1->_year > d2->_year) ||
(d1->_year == d2->_year && d1->_month > d2->_month) ||
(d1->_year == d2->_year && d1->_month == d2->_month && d1->_day > d2->_day);
}
};
附:priority_queue.h
如果你被东一块儿西一块儿的代码搞晕了哈哈,欢迎通读.h文件 ——
#pragma once
#include<iostream>
#include<vector>
using namespace std;
namespace beatles
{
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 Compare = less<T>>
class priority_queue
{
private:
void ajust_up(size_t child)
{
while (child > 0)
{
size_t parent = (child - 1) / 2;
//if (_con[parent] < _con[child]) //默认大堆的情况下
if (Compare()(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
}
else
{
break;
}
}
}
void ajust_down(size_t parent)
{
size_t child = parent * 2 + 1;
while (child < _con.size())
{
//if (child + 1 < _con.size() && _con[child] < _con[child + 1]) //默认大堆的情况下
if (child + 1 < _con.size() && Compare()(_con[child], _con[child + 1]))
child++;
//if (_con[parent],<_con[child]) //默认大堆的情况下
if (Compare()(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
public:
priority_queue()
{}
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
: _con(first,last)
{
//构建堆结构
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
ajust_down(i);
}
}
void push(const T& x)
{
_con.push_back(x);
ajust_up(_con.size() - 1);
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
ajust_down(0);
}
const T& top() const
{
return _con[0];
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
// 测试尾插尾删,同时测试了向上调整&向下调整算法
void test_priority_queue1()
{
//priority_queue<int> pq; //默认大的优先级高,仿函数为less
priority_queue<int, vector<int>, greater<int>> pq;
pq.push(1);
pq.push(3);
pq.push(5);
pq.push(2);
pq.push(4);
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
class Date
{
friend struct lessPDate;
friend struct greaterPDate;
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);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
// 对于自定义类型,可以重载>和<
// 如果数据类型不支持比较 或 比较的方式不是你想要的
// 可以自己实现 仿函数,按照你想要的方式去控制比较逻辑
void test_priority_queue2()
{
//priority_queue<Date> pq;
priority_queue<Date, vector<Date>, greater<Date>> pq;
pq.push(Date(2022, 3, 26));
pq.push(Date(2022, 4, 1));
pq.push(Date(2022, 2, 19));
pq.push(Date(2022, 4, 10));
while (!pq.empty())
{
cout << pq.top() << endl;
pq.pop();
}
}
struct lessPDate
{
bool operator()(const Date* d1,const Date* d2)
{
//return *d1 < *d2;
return (d1->_year < d2->_year) ||
(d1->_year == d2->_year && d1->_month < d2->_month) ||
(d1->_year == d2->_year && d1->_month == d2->_month && d1->_day < d2->_day);
}
};
struct greaterPDate
{
bool operator()(const Date* d1, const Date* d2)
{
//return *d1 > *d2;
return (d1->_year > d2->_year) ||
(d1->_year == d2->_year && d1->_month > d2->_month) ||
(d1->_year == d2->_year && d1->_month == d2->_month && d1->_day > d2->_day);
}
};
void test_priority_queue3()
{
//priority_queue<Date*> pq; //默认比较地址大小
//priority_queue<Date*, vector<Date*>, lessPDate> pq;
priority_queue<Date*, vector<Date*>, greaterPDate> pq;
pq.push(new Date(2022, 3, 26));
pq.push(new Date(2022, 4, 1));
pq.push(new Date(2022, 2, 19));
pq.push(new Date(2022, 4, 10));
//默认比较地址大小,若想比较日期大小,自己写仿函数
while (!pq.empty())
{
cout << *pq.top() << endl;
pq.pop();
}
}
}
持续更新@边通书