优先队列(priority queue)
目录一,优先队列二,二叉堆实现优先队列三,二项堆实现优先队列一,优先队列优先队列可以插入元素、查询最大值、删除最大值。优先队列其实就是最大堆或最小堆,最大堆可以用来实现最大优先队列,最小堆可以用来实现最小优先队列。用各种堆都可以实现优先队列。二,二叉堆实现优先队列二叉堆:https://blog.csdn.net/nameofcsdn/article/details/113484808最大优先队列
目录
一,优先队列
优先队列可以插入元素、查询最大值、删除最大值。
优先队列其实就是最大堆或最小堆,最大堆可以用来实现最大优先队列,最小堆可以用来实现最小优先队列。
用各种堆都可以实现优先队列。
二,STL优先队列
优先队列如果要自定义排序函数,只能用仿函数,不能用普通函数的指针。
示例:
#include<iostream>
#include<queue>
#include<functional>
using namespace std;
class cmp
{
public:
bool operator()(int a, int b)
{
return a < b;
}
};
bool cmp2(int a, int b)
{
return a < b;
}
template <typename A>
void display()
{
A q;
q.push(1);
q.push(3);
q.push(4);
q.push(2);
while (!q.empty())
{
cout << q.top();
q.pop();
}
cout << endl;
}
int main()
{
display < priority_queue<int, vector<int>> >(); //默认大顶堆
display < priority_queue <int, vector<int>, greater<int>> >();//小顶堆
display < priority_queue <int, vector<int>, less<int>> >();//大顶堆
display < priority_queue<int, vector<int>, cmp>>();//大顶堆
//display < priority_queue<int, vector<int>, cmp2>>(); 错误
return 0;
}
输出:
4321
1234
4321
4321
附上priority_queue的实现代码:
// TEMPLATE CLASS priority_queue
template<class _Ty,
class _Container = vector<_Ty>,
class _Pr = less<typename _Container::value_type> >
class priority_queue
{ // priority queue implemented with a _Container
public:
......
一堆函数
......
protected:
_Container c; // the underlying container
_Pr comp; // the comparator functor
};
可以看出模板参数_Pr的默认值是less,也就是说默认排序函数是less函数。
priority_queue中包含一个排序的序列,堆顶是这个序列的最后一个元素,所以从小到大排序是大顶堆,从大到小排序是小顶堆。
三,二叉堆实现优先队列
最大优先队列:
#include<iostream>
#include <vector>
using namespace std;
template<typename T>
bool cmp(T a, T b)
{
return a < b; //最大堆
}
template<typename T>
void exchange(T* a, T* b)
{
T tmp = *a;
*a = *b;
*b = tmp;
}
int LeftChild(int id)
{
return id * 2;
}
int RightChild(int id)
{
return id * 2 + 1;
}
int Parent(int id)
{
return id/2;
}
template<typename T>
void AdjustHeap(T* arr, int rootId, int size)
{
int largest = rootId, left = LeftChild(rootId), right = RightChild(rootId);
if (left < size && cmp(arr[largest], arr[left]))largest = left;
if (right < size && cmp(arr[largest], arr[right]))largest = right;
if (largest == rootId)return;
exchange(arr + rootId, arr + largest);
AdjustHeap(arr, largest, size);
}
template<typename T>
void HeapIncrese(T* arr, int size, int id, T newValue)
{
arr[id] = newValue;
while (id > 0 && cmp(arr[Parent(id)], arr[id])) {
exchange(arr + id, arr + Parent(id));
id = Parent(id);
}
}
template<typename T>
void HeapInsert(T* arr, int &size,T value)
{
HeapIncrese(arr,size+1,size,value);
size++;
}
int g_size;
void Init()
{
g_size = 0;
}
template<typename T>
T Top(T* arr)
{
return arr[0];
}
template<typename T>
void Push(T* arr, T value)
{
HeapInsert(arr,g_size,value);
}
template<typename T>
void Pop(T* arr)
{
g_size--;
exchange(arr, arr+g_size);
AdjustHeap(arr, 0, g_size);
}
int main()
{
int q[10];
Init();
Push(q,4);
Push(q,2);
Push(q,6);
Push(q,3);
Push(q,4);
Push(q,5);
cout<<Top(q)<<" ";
Pop(q);
cout<<Top(q)<<" ";
Pop(q);
cout<<Top(q)<<" ";
Pop(q);
cout<<Top(q)<<" ";
Pop(q);
cout<<Top(q)<<" ";
Pop(q);
cout<<Top(q)<<" ";
Pop(q);
cout<<endl<<g_size;
return 0;
}
运行结果:
6 5 4 4 3 2
0
四,二项堆实现优先队列
二项堆 里面已经实现了push、pop、top等操作,所以它就是一种优先队列。
五,线段树实现优先队列
代码:
struct anode
{
int t;
int d;
};
int n;
int num[10001],maxx[40001];
struct anode node[10001];
void build(int key, int low, int high)//id
{
if (low == high)
{
maxx[key] = low;
return;
}
int mid = (low + high) / 2;
build(key * 2, low, mid);
build(key * 2 + 1, mid + 1, high);
maxx[key] = (num[maxx[key * 2]] > num[maxx[key * 2 + 1]]) ? maxx[key * 2] : maxx[key * 2 + 1];
}
void update(int key, int low, int high, int uplace)//id
{
if (low == high)
{
maxx[key] = low;
return;
}
int mid = (low + high) / 2;
if (uplace <= mid)update(key * 2, low, mid, uplace);
else update(key * 2 + 1, mid + 1, high, uplace);
maxx[key] = (num[maxx[key * 2]] > num[maxx[key * 2 + 1]]) ? maxx[key * 2] : maxx[key * 2 + 1];
}
int query(int key, int low, int high, int x, int y)//id
{
if (low == x && high == y)return maxx[key];
int mid = (low + high) / 2;
if (mid < x)return query(key * 2 + 1, mid + 1, high, x, y);
if (mid >= y)return query(key * 2, low, mid, x, y);
int a = query(key * 2, low, mid, x, mid);
int b = query(key * 2 + 1, mid + 1, high, mid + 1, y);
return (num[a]>num[b]) ? a : b;
}
void push(int loc)
{
num[loc]=node[loc].t; ///根据实际情况修改
update(1,1,n,loc);
}
void pop(int loc)
{
num[loc]=0; ///根据实际情况修改
update(1,1,n,loc);
}
int top()//id
{
return query(1,1,n,1,n);
}
应用:
六,OJ实战
力扣 295. 数据流的中位数
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例:
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
思路:
维护1个最大堆和1个最小堆,并动态维持他们的尺寸相同。
class MedianFinder {
public:
MedianFinder() {
}
void addNum(int num) {
if (qmax.empty()||num<=qmax.top())qmax.push(num);
else qmin.push(num);
}
double findMedian() {
while (qmin.size() > qmax.size()) qmax.push(qmin.top()),qmin.pop();
while (qmax.size() > qmin.size()+1)qmin.push(qmax.top()),qmax.pop();
if (qmax.size() > qmin.size())return qmax.top();
return (qmax.top() + qmin.top()) / 2.0;
}
private:
priority_queue<int, vector<int>> qmax;
priority_queue <int, vector<int>, greater<int>> qmin;
};
或者用自己实现的堆:
template<typename T>
class MyHeap {
public:
T arr[100000];
MyHeap(int type)//0最小堆1最大堆
{
this->type = type;
}
T Top()
{
return arr[0];
}
void Push(T value)
{
HeapInsert(arr, value);
}
void Pop()
{
size--;
exchange(arr, arr + size);
AdjustHeap(arr, 0);
}
bool empty()
{
return size == 0;
}
int Size()
{
return size;
}
private:
void AdjustHeap(T* arr, int rootId)
{
int largest = rootId, left = LeftChild(rootId), right = RightChild(rootId);
if (left < size && cmp(arr[largest], arr[left]))largest = left;
if (right < size && cmp(arr[largest], arr[right]))largest = right;
if (largest == rootId)return;
exchange(arr + rootId, arr + largest);
AdjustHeap(arr, largest);
}
void HeapIncrese(T* arr, int id, T newValue)
{
arr[id] = newValue;
while (id > 0 && cmp(arr[Parent(id)], arr[id])) {
exchange(arr + id, arr + Parent(id));
id = Parent(id);
}
}
void HeapDecrese(T* arr, int size, int id, T newValue)
{
arr[id] = newValue;
AdjustHeap(arr, id, size);
}
void HeapChange(T* arr, int id, T newValue)
{
if (cmp(arr[id], newValue))HeapIncrese(arr, id, newValue);
else HeapDecrese(arr, id, newValue);
}
void HeapInsert(T* arr, T value)
{
HeapIncrese(arr, size++, value);
}
bool cmp(T a, T b)
{
return type ? (a < b) : (a > b);
}
void exchange(T* a, T* b)
{
T tmp = *a;
*a = *b;
*b = tmp;
}
int LeftChild(int id)
{
return id * 2 + 1;
}
int RightChild(int id)
{
return id * 2 + 2;
}
int Parent(int id)
{
return (id - 1) / 2;
}
int type;
int size = 0;
};
class MedianFinder {
public:
MedianFinder() {
}
void addNum(int num) {
if (qmax.empty() || num <= qmax.Top())qmax.Push(num);
else qmin.Push(num);
}
double findMedian() {
while (qmin.Size() > qmax.Size()) qmax.Push(qmin.Top()), qmin.Pop();
while (qmax.Size() > qmin.Size() + 1)qmin.Push(qmax.Top()), qmax.Pop();
if (qmax.Size() > qmin.Size())return qmax.Top();
return (qmax.Top() + qmin.Top()) / 2.0;
}
private:
MyHeap<int> qmax{ 1 };
MyHeap<int> qmin{ 0 };
};
力扣 LCP 24. 数字游戏
小扣在秋日市集入口处发现了一个数字游戏。主办方共有 N
个计数器,计数器编号为 0 ~ N-1
。每个计数器上分别显示了一个数字,小扣按计数器编号升序将所显示的数字记于数组 nums
。每个计数器上有两个按钮,分别可以实现将显示数字加一或减一。小扣每一次操作可以选择一个计数器,按下加一或减一按钮。
主办方请小扣回答出一个长度为 N
的数组,第 i
个元素(0 <= i < N)表示将 0~i
号计数器 初始 所示数字操作成满足所有条件 nums[a]+1 == nums[a+1],(0 <= a < i)
的最小操作数。回答正确方可进入秋日市集。
由于答案可能很大,请将每个最小操作数对 1,000,000,007
取余。
示例 1:
输入:
nums = [3,4,5,1,6,7]
输出:
[0,0,0,5,6,7]
解释: i = 0,[3] 无需操作 i = 1,[3,4] 无需操作; i = 2,[3,4,5] 无需操作; i = 3,将 [3,4,5,1] 操作成 [3,4,5,6], 最少 5 次操作; i = 4,将 [3,4,5,1,6] 操作成 [3,4,5,6,7], 最少 6 次操作; i = 5,将 [3,4,5,1,6,7] 操作成 [3,4,5,6,7,8],最少 7 次操作; 返回 [0,0,0,5,6,7]。
示例 2:
输入:
nums = [1,2,3,4,5]
输出:
[0,0,0,0,0]
解释:对于任意计数器编号 i 都无需操作。
示例 3:
输入:
nums = [1,1,1,2,3,4]
输出:
[0,1,2,3,3,3]
解释: i = 0,无需操作; i = 1,将 [1,1] 操作成 [1,2] 或 [0,1] 最少 1 次操作; i = 2,将 [1,1,1] 操作成 [1,2,3] 或 [0,1,2],最少 2 次操作; i = 3,将 [1,1,1,2] 操作成 [1,2,3,4] 或 [0,1,2,3],最少 3 次操作; i = 4,将 [1,1,1,2,3] 操作成 [-1,0,1,2,3],最少 3 次操作; i = 5,将 [1,1,1,2,3,4] 操作成 [-1,0,1,2,3,4],最少 3 次操作; 返回 [0,1,2,3,3,3]。
提示:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^3
思路:
把力扣 295. 数据流的中位数中的MedianFinder进行改造,数据流的形式不变,把查询中位数改成查询所有数到中位数的距离总和。
实现的思路不变,时间复杂度也不变。
利用改造后的MedianFinder,直接可以得到本题的答案。
class MedianFinder {
public:
MedianFinder() {
}
void addNum(int num) {
if (qmax.empty() || num <= qmax.top()) {
if (qmax.size() == qmin.size()) {
qmax.push(num);
s += qmax.top() - num;
}
else {
qmin.push(qmax.top()), qmax.pop();
qmax.push(num);
s += qmin.top() - num;
}
}
else {
if (qmax.size() == qmin.size()) {
qmin.push(num);
s += num - qmin.top();
qmax.push(qmin.top()), qmin.pop();
}
else {
qmin.push(num);
s += num - qmax.top();
}
}
}
long long sumLen() {
return s;
}
private:
long long s = 0;
priority_queue<int, vector<int>> qmax;//最大堆放小的一半数据
priority_queue <int, vector<int>, greater<int>> qmin;//最小堆放大的一半数据
};
class Solution {
public:
vector<int> numsGame(vector<int>& nums) {
MedianFinder opt;
vector<int>ans;
for(int i=0;i<nums.size();i++){
opt.addNum(nums[i]-i);
ans.push_back(opt.sumLen() % 1000000007);
}
return ans;
}
};
力扣 LCP 30. 魔塔游戏
小扣当前位于魔塔游戏第一层,共有 N
个房间,编号为 0 ~ N-1
。每个房间的补血道具/怪物对于血量影响记于数组 nums
,其中正数表示道具补血数值,即血量增加对应数值;负数表示怪物造成伤害值,即血量减少对应数值;0
表示房间对血量无影响。
小扣初始血量为 1,且无上限。假定小扣原计划按房间编号升序访问所有房间补血/打怪,为保证血量始终为正值,小扣需对房间访问顺序进行调整,每次仅能将一个怪物房间(负数的房间)调整至访问顺序末尾。请返回小扣最少需要调整几次,才能顺利访问所有房间。若调整顺序也无法访问完全部房间,请返回 -1。
示例 1:
输入:
nums = [100,100,100,-250,-60,-140,-50,-50,100,150]
输出:
1
解释:初始血量为 1。至少需要将 nums[3] 调整至访问顺序末尾以满足要求。
示例 2:
输入:
nums = [-200,-300,400,0]
输出:
-1
解释:调整访问顺序也无法完成全部房间的访问。
提示:
1 <= nums.length <= 10^5
-10^5 <= nums[i] <= 10^5
class Solution {
public:
int magicTower(vector<int>& nums) {
priority_queue<int, vector<int>>q;
long long s = 0, s2 = 0, n = 0;
for (auto x : nums) {
if (x < 0)q.push(-x);
s += x;
if (s < 0) {
s += q.top();
s2 += q.top();
q.pop();
n++;
}
}
if (s - s2 >= 0)return n;
return -1;
}
};
力扣 2462. 雇佣 K 位工人的总代价
给你一个下标从 0 开始的整数数组 costs
,其中 costs[i]
是雇佣第 i
位工人的代价。
同时给你两个整数 k
和 candidates
。我们想根据以下规则恰好雇佣 k
位工人:
- 总共进行
k
轮雇佣,且每一轮恰好雇佣一位工人。 - 在每一轮雇佣中,从最前面
candidates
和最后面candidates
人中选出代价最小的一位工人,如果有多位代价相同且最小的工人,选择下标更小的一位工人。- 比方说,
costs = [3,2,7,7,1,2]
且candidates = 2
,第一轮雇佣中,我们选择第4
位工人,因为他的代价最小[3,2,7,7,1,2]
。 - 第二轮雇佣,我们选择第
1
位工人,因为他们的代价与第4
位工人一样都是最小代价,而且下标更小,[3,2,7,7,2]
。注意每一轮雇佣后,剩余工人的下标可能会发生变化。
- 比方说,
- 如果剩余员工数目不足
candidates
人,那么下一轮雇佣他们中代价最小的一人,如果有多位代价相同且最小的工人,选择下标更小的一位工人。 - 一位工人只能被选择一次。
返回雇佣恰好 k
位工人的总代价。
示例 1:
输入:costs = [17,12,10,2,7,2,11,20,8], k = 3, candidates = 4 输出:11 解释:我们总共雇佣 3 位工人。总代价一开始为 0 。 - 第一轮雇佣,我们从 [17,12,10,2,7,2,11,20,8] 中选择。最小代价是 2 ,有两位工人,我们选择下标更小的一位工人,即第 3 位工人。总代价是 0 + 2 = 2 。 - 第二轮雇佣,我们从 [17,12,10,7,2,11,20,8] 中选择。最小代价是 2 ,下标为 4 ,总代价是 2 + 2 = 4 。 - 第三轮雇佣,我们从 [17,12,10,7,11,20,8] 中选择,最小代价是 7 ,下标为 3 ,总代价是 4 + 7 = 11 。注意下标为 3 的工人同时在最前面和最后面 4 位工人中。 总雇佣代价是 11 。
示例 2:
输入:costs = [1,2,4,1], k = 3, candidates = 3 输出:4 解释:我们总共雇佣 3 位工人。总代价一开始为 0 。 - 第一轮雇佣,我们从 [1,2,4,1] 中选择。最小代价为 1 ,有两位工人,我们选择下标更小的一位工人,即第 0 位工人,总代价是 0 + 1 = 1 。注意,下标为 1 和 2 的工人同时在最前面和最后面 3 位工人中。 - 第二轮雇佣,我们从 [2,4,1] 中选择。最小代价为 1 ,下标为 2 ,总代价是 1 + 1 = 2 。 - 第三轮雇佣,少于 3 位工人,我们从剩余工人 [2,4] 中选择。最小代价是 2 ,下标为 0 。总代价为 2 + 2 = 4 。 总雇佣代价是 4 。
提示:
1 <= costs.length <= 105
1 <= costs[i] <= 105
1 <= k, candidates <= costs.length
struct Node
{
int x, id;
};
class cmp
{
public:
bool operator()(Node a, Node b)
{
if (a.x == b.x)return a.id > b.id;
return a.x > b.x;
}
};
class Solution {
public:
long long totalCost(vector<int>& costs, int k, int candidates) {
priority_queue<Node, vector<Node>, cmp>q;
int low = 0, high = costs.size() - 1;
while (low < candidates && low < costs.size()) {
q.push(Node{ costs[low],low++ });
}
while (high >= costs.size() - candidates && high >= low) {
q.push(Node{ costs[high],high-- });
}
long long ans = 0;
while (!q.empty() && k--) {
Node nod = q.top();
q.pop();
ans += nod.x;
if (low <= high) {
if(nod.id<low)q.push(Node{ costs[low],low++ });
else q.push(Node{ costs[high],high-- });
}
}
return ans;
}
};
力扣 632. 最小区间
你有 k
个 非递减排列 的整数列表。找到一个 最小 区间,使得 k
个列表中的每个列表至少有一个数包含在其中。
我们定义如果 b-a < d-c
或者在 b-a == d-c
时 a < c
,则区间 [a,b]
比 [c,d]
小。
示例 1:
输入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]] 输出:[20,24] 解释: 列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。 列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。 列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。
示例 2:
输入:nums = [[1,2,3],[1,2,3],[1,2,3]] 输出:[1,1]
提示:
nums.length == k
1 <= k <= 3500
1 <= nums[i].length <= 50
-105 <= nums[i][j] <= 105
nums[i]
按非递减顺序排列
struct Node {
int x, id;
};
class cmp
{
public:
bool operator()(Node a, Node b)
{
return a.x > b.x;
}
};
class Solution {
public:
vector<int> smallestRange(vector<vector<int>>& nums) {
vector<int>ids(nums.size(), 0);
vector<int>ans{-100000,100000};
priority_queue<Node, vector<Node>, cmp>q;//最小堆
int minMax = 100000;
int maxid = 0;
for (int i = 0; i < ids.size(); i++) {
if (nums[i][0] > nums[maxid][0])maxid = i;
minMax = min(minMax, nums[i][nums[i].size() - 1]);
q.push(Node{ nums[i][0],i });
}
while (true) {
int t = q.top().x;
int minid = q.top().id;
q.pop();
if (ans[1] - ans[0] > nums[maxid][ids[maxid]] - nums[minid][ids[minid]] ||
(ans[1] - ans[0] == nums[maxid][ids[maxid]] - nums[minid][ids[minid]] && ans[0] > nums[minid][ids[minid]])) {
ans = vector<int>{ nums[minid][ids[minid]] ,nums[maxid][ids[maxid]] };
}
if (ids[minid] + 1 >= nums[minid].size())return ans;
ids[minid]++;
if (nums[minid][ids[minid]] > nums[maxid][ids[maxid]])maxid = minid;
q.push(Node{ nums[minid][ids[minid]],minid });
}
return ans;
}
};
力扣 857. 雇佣 K 名工人的最低成本
有 n
名工人。 给定两个数组 quality
和 wage
,其中,quality[i]
表示第 i
名工人的工作质量,其最低期望工资为 wage[i]
。
现在我们想雇佣 k
名工人组成一个工资组。在雇佣 一组 k
名工人时,我们必须按照下述规则向他们支付工资:
- 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。
- 工资组中的每名工人至少应当得到他们的最低期望工资。
给定整数 k
,返回 组成满足上述条件的付费群体所需的最小金额 。在实际答案的 10-5
以内的答案将被接受。。
示例 1:
输入: quality = [10,20,5], wage = [70,50,30], k = 2 输出: 105.00000 解释: 我们向 0 号工人支付 70,向 2 号工人支付 35。
示例 2:
输入: quality = [3,1,10,10,1], wage = [4,8,2,2,7], k = 3 输出: 30.66667 解释: 我们向 0 号工人支付 4,向 2 号和 3 号分别支付 13.33333。
提示:
n == quality.length == wage.length
1 <= k <= n <= 104
1 <= quality[i], wage[i] <= 104
struct Node
{
int q, w;
};
bool cmp(Node a, Node b)
{
return a.w * b.q < b.w * a.q;
}
class cmp2
{
public:
bool operator()(Node a, Node b)
{
return a.q < b.q;
}
};
class Solution {
public:
double mincostToHireWorkers(vector<int>& quality, vector<int>& wage, int k) {
vector<Node>v;
for (int i = 0; i < quality.size(); i++) {
v.push_back(Node{ quality[i],wage[i] });
}
sort(v.begin(), v.end(), cmp);
priority_queue<Node, vector<Node>, cmp2>q;
int s = 0;
for (int i = 0; i < k; i++) {
s += v[i].q;
q.push(v[i]);
}
double ans = v[k - 1].w * 1.0 / v[k - 1].q * s;
for (int i = k; i < v.size(); i++) {
s+= v[i].q;
q.push(v[i]);
auto nod = q.top();
q.pop();
s -= nod.q;
ans = min(ans, v[i].w * 1.0 / v[i].q * s);
}
return ans;
}
};
其他应用
CSU 1588 合并果子
CCF-CSP-2017-3-4 地铁修建
力扣 347. 前 K 个高频元素
力扣 373. 查找和最小的K对数字
力扣 378. 有序矩阵中第K小的元素
力扣 1090. 受标签影响的最大值
力扣 1631. 最小体力消耗路径
力扣 451. 根据字符出现频率排序
2020编码大赛(2)初赛
更多推荐
所有评论(0)