priority_queue的介绍及使用
💦 priority_queue的介绍
-
优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
-
此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素 (优先队列中位于顶部的元素)。
-
优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue 提供一组特定的成员函数来访问其元素。元素从特定容器的 “ 尾部 ” 弹出,其称为优先队列的顶部。
-
底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
➡ empty():检测容器是否为空。
➡ size():返回容器中有效元素个数。
➡ front():返回容器中第一个元素的引用。
➡ push_back():在容器尾部插入元素。
➡ pop_back():删除容器尾部元素。 -
标准容器类 vector 和 deque 满足这些需求。默认情况下,如果没有为特定的 priority_queue 类实例化指定容器类,则使用 vector。
-
需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap 和pop_heap 来自动完成此操作。
注意这里的优先级队列不符合队列的先进先出特性,它的 push 没有要求,pop/top 是取优先级最高的,优先级指的是大小,这里默认是大的优先级高,如果要让小的优先级高,就需要仿函数来控制。但要注意并不一定是大的优先级高,小的优先级低,因为对于用数字来评分 (大的高) 是一种场景,对于用字母来评分 (小的高) 又是一种场景。
💦 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() | 删除优先级队列中最大 (最小) 元素,即堆顶元素 |
#include<iostream>
#include<queue>
#include<functional> //greater的头
using namespace std;
void test_priority_queue()
{
//priority_queue<int> pq;//默认是大堆,大的优先级高
priority_queue<int,vector<int>, greater<int>> pq;//默认是小堆,小的优先级高。控制大小堆的是第3个参数,你要传第3个参数,必须先传第2个参数,因为它也是缺省的
pq.push(1);
pq.push(10);
pq.push(11);
pq.push(3);
pq.push(5);
pq.push(8);
while(!pq.empty())
{
//取堆顶的数据
cout << pq.top() << " ";
//出堆顶(与最后元素交换,再删除它),向下调整
pq.pop();
}
cout << endl;
}
int main()
{
test_priority_queue();
return 0;
}
💦 priority_queue的OJ
1、数组中的第K个最大元素<难度系数⭐⭐>
📝 题述:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
💨示例1:
输入:[3,2,1,5,6,4] 和 k = 2
输出:5
💨示例2:
输入:[3,2,3,1,2,4,5,5,6] 和 k = 4
输出:4
⚠ 提示:
- 1 <= k <= nums.length <= 10^4^
- -10^4^ <= nums[i] <= 10^4^
🧷 平台:Visual studio 2017 && windows
🔑 核心思想:这道题是一道 top-k 的变形问题。这里用 C++ 来做就非常简单,有如下方案:
-
先调用 <algorithm> 中的 sort 对数组排升序,随后算倒数第 k 大就是正数的 nums.size() - k 为下标的位置。当然直接排降序也可以,其底层是快排。时间复杂度为 O(N*logN)。无空间复杂度。
-
用 priority_queue 建一个大堆,然后pop k - 1 个,再 top() 就是第 k 大的。把 nums 的数据往 maxHeap 里放有 2 种方式。时间复杂度为 O(N + K * logN),N 是向下调整建堆,K * logN 是 pop。空间复杂度 O(N)。
➡ 遍历nums,一个个 push。
➡ 构造函数里提供迭代器区间初始化。 -
建一个 k 个数的小堆,其它数依次比较,它比堆顶的数要大,那么就 pop 堆顶,再 push 这个数,最后堆顶的数据就是第 k 大的。时间复杂度是 O(K + (N - K) * logK),空间复杂度是 O(K)。就目前的数据量来说,其实第二种方案和第三种方案的效率差不多,但是当 N 远大于 K 时,尤其是大到内存存不下 (方案一、二都不能适用),第三种方案更适用,此时可以认为它的时间复杂度是 O(N - K) ➡ O(N)。
🧿 方案一
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
// //RandAccessIterator,sort默认是升序
// sort(nums.begin(), nums.end());
// //此时倒数第k大就是正数的nums.size()-k为下标的位置。
// return nums[nums.size() - k];
//sort排降序
//greater<int> g;
//sort(nums.begin(), nums.end(), g);
sort(nums.begin(), nums.end(), greater<int>());//同上,匿名对象
//此时k-1作为下标就是第k大的
return nums[k - 1];
}
};
🧿 方案二
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> maxHeap(nums.begin(), nums.end());
while(k-=1)//同k-=1
{
maxHeap.pop();
}
//此时堆顶既是第k大
return maxHeap.top();
}
};
🧿 方案三
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
//建k个数的小堆
priority_queue<int, vector<int>, greater<int>> kMinHeap;
for(size_t i = 0; i < k; ++i)
{
kMinHeap.push(nums[i]);
}
//再用剩余的数比较
for(size_t j = k; j < nums.size(); ++j)
{
if(kMinHeap.top() < nums[j])
{
kMinHeap.pop();
kMinHeap.push(nums[j]);
}
}
//此时的堆顶就是第k大的
return kMinHeap.top();
}
};
- 点赞
- 收藏
- 关注作者
评论(0)