目录
- 1.priority_queue的介绍和使用
-
- 1.1 priority_queue的介绍
- 1.2 priority_queue的使用
- 1.3 在OJ中的使用
- 1.4 priority_queue的模拟实现
-
- 仿函数/函数对象
- 向上调整
- 向下调整
1.priority_queue的介绍和使用
1.1 priority_queue的介绍
- 优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
- 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
- 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
- 优先级队列被实现为容器适配器,容器适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素只能从特定容器的顶部弹出。
- 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
- empty():检测容器是否为空
- size():返回容器中有效元素个数
- front():返回容器中第一个元素的引用
- push_back():在容器尾部插入元素
- pop_back():删除容器尾部元素
- 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
- 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。
1.2 priority_queue的使用
优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆。
函数声明 | 接口说明 |
---|---|
priority_queue()/priority_queue(first,last) | 构造一个空的优先级队列 |
empty( ) | 检测优先级队列是否为空,是返回true,否则返回false |
top( ) | 返回优先级队列中最大(最小元素),即堆顶元素 |
push(x) | 在优先级队列中插入元素x |
pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |
【注意】
- 默认情况下,priority_queue是大堆。
- 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。
1.3 在OJ中的使用
数组中的第K个最大元素
思路:
1、建大堆:把数组中的n个数全部放到默认的优先级队列pq里,此时pq就是一个n个数的大堆,然后让优先级队列pop K次,此时堆顶的元素就是数组中的第K个最大元素。
2、建小堆:先把数组的前K个数push到优先级队列pq里(这里比较容易出错),然后从K+1个数开始遍历数组,如果遇到比pq的堆顶元素大的数,那么就先把堆顶pop掉然后把这个数push到pq里,这样遍历到最后一个数,此时pq中的K个元素就是前K大的K个数,堆顶就是数组中的第K个最大元素。
代码:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
// //用默认的去 建大堆,然后pop k次
// priority_queue<int> pq (nums.begin(),nums.end());
// //找第k个最大的数pop k-1次。
// while(--k)
// {
// pq.pop();
// }
// return pq.top();
//建小堆
priority_queue<int,vector<int>,greater<int>> pq
(nums.begin(),nums.begin()+k);
//容易出错的地方nums.begin()到nums.begin()+k,才是k个元素
for(int i=k;i<nums.size();i++)
{
if(nums[i]>pq.top())
{
pq.pop();
pq.push(nums[i]);
}
}
return pq.top();
}
};
1.4 priority_queue的模拟实现
通过对priority_queue的底层结构就是堆,因此此处只需对vector或者deque这样的容器进行通用的封装,把他们按照堆的模式封装即可。
仿函数/函数对象
仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator()
,也就是对于()
的运算符重载,这个类就有了类似函数的行为,就是一个仿函数类了。目的是为了代替C语言中的函数指针
运用在排序中用户可以自己定义升序或者降序。
对应到堆里面就是大堆或是小堆。
标准库仿函数的模拟实现:
//仿函数/函数对象
template<class T>
class less
{
public:
bool operator()(const T& x, const T& y) const
{
return x < y;
}
};
template<class T>
class greater
{
public:
bool operator()(const T& x, const T& y) const
{
return x > y;
}
};
- 对于内置类型,天然的就支持<和>这样比大小的操作。
- 对于自定义类型,如果类里重载了<和>运算符,支持类与类之间的<和>操作,并且功能符合预期,那下面代码中的<和>就转化为对应的运算符重载进行比较即可;若功能不满足我们的预期,那么需要自己去实现对应的仿函数。
功能不满足预期的举例:
//日期类
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;
};
struct PDateLess//需要自己写仿函数,这里是自己实现的仿函数
{
bool operator()(const Date* d1, const Date* d2)
{
return *d1 < *d2;
}
};
struct PDateGreater//需要自己写仿函数,这里是自己实现的仿函数
{
bool operator()(const Date* d1, const Date* d2)
{
return *d1 > *d2;
}
};
void TestPriorityQueue()
{
// 大堆
priority_queue<Date*, vector<Date*>, PDateLess> q3;
//定义类的时候用的是PDateLess——仿函数的类型
q3.push(new Date(2018, 10, 29));
q3.push(new Date(2018, 10, 28));
q3.push(new Date(2018, 10, 30));
cout << *q3.top() << endl;
// 小堆
priority_queue<Date*, vector<Date*>, PDateGreater> q4;
//定义类的时候用的是PDateGreater——仿函数的类型
q4.push(new Date(2018, 10, 29));
q4.push(new Date(2018, 10, 28));
q4.push(new Date(2018, 10, 30));
cout << *q4.top() << endl;
}
int main()
{
TestPriorityQueue();
return 0;
}
仿函数的使用:不管是用库里面的还是自己实现的仿函数,在定义一个类的时候,用的是仿函数的类型(一般类名就是类型,但是在模板类中需要加上模板参数才是一个完整的类型),在类里会拿这个类型去实例化一个对象,通过这个对象来调用对于()
的运算符重载,以此来达到控制类里对应的行为。
下面是类里向上调整和向下调整的代码:
向上调整
void adjust_up(size_t child)
{
Compare com;//实例化一个对象com
size_t parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[parent] < con[child])
if (com(_con[parent], _con[child]))//通过这个对象com来调用对于()的运算符重载
{
swap(_con[child], _con[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
向下调整
void adjust_down(size_t parent)
{
Compare com;//实例化一个对象com
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() && com(_con[child], _con[child + 1]))//通过对象com来调用对于()的运算符重载
{
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;
}
}
}
如果对堆其他知识有不清楚的老铁可以看这篇文章😊
(C语言)数据结构二叉树之堆
版权声明:本文为博主作者:有效的放假者原创文章,版权归属原作者,如果侵权,请联系我们删除!
原文链接:https://blog.csdn.net/qq_55712347/article/details/128874870