priority_queue的使用
std::priority_queue
是 C++ 标准模板库(STL)中的一个容器适配器,提供了优先队列的功能。
优先队列:是一种特殊的队列,队列中的每个元素都有与之关联的优先级,优先级高的元素会先出队,而不是像普通队列那样遵循先进先出(FIFO)原则。
使用场景:在vector上又使用了堆算法将vector中的元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。
priority_queue的定义方式
方式一: 使用vector作为底层容器,内部构造大堆结构。
priority_queue<int, vector<int>, less<int>> q1;
方式二: 使用vector作为底层容器,内部构造小堆结构。
priority_queue<int, vector<int>, greater<int>> q2;
方式三: 不指定底层容器和内部需要构造的堆结构。(默认情况下priority_queue是大堆)
priority_queue<int> q;
注意⚠️:当我们调用less时是大堆,greater是小堆。(并不知道祖师爷是怎么想的)
功能 | 接口 | 描述 | 复杂度 | 示例代码 |
---|---|---|---|---|
插入元素 | push(const T& value) |
向优先队列插入一个元素,插入后自动调整维持堆性质 | O(logn) | std::priority_queue<int> pq; pq.push(5); |
获取堆顶元素 | top() |
返回优先队列的队首元素(大顶堆为最大值,小顶堆为最小值) | O(1) | std::priority_queue<int> pq; pq.push(5); int top = pq.top(); |
移除堆顶元素 | pop() |
移除优先队列的队首元素,移除后重新调整堆结构 | O(logn) | std::priority_queue<int> pq; pq.push(5); pq.pop(); |
获取元素数量 | size() |
返回优先队列中元素的数量 | O(1) | std::priority_queue<int> pq; pq.push(5); size_t s = pq.size(); |
检查是否为空 | empty() |
检查优先队列是否为空,空则返回 true ,否则返回 false |
O(1) | std::priority_queue<int> pq; bool isEmpty = pq.empty(); |
#include <iostream>
#include <functional>
#include <queue>
using namespace std;
int main()
{
priority_queue<int> q;
q.push(3);
q.push(6);
q.push(0);
q.push(2);
q.push(9);
q.push(8);
q.push(1);
while (!q.empty())
{
cout << q.top() << " ";
q.pop();
}
cout << endl; //9 8 6 3 2 1 0
return 0;
}
priority_queue的模拟实现
priority_queue的底层实际上就是堆结构,实现priority_queue之前,我们先认识两个重要的堆算法。
注意⚠️:我以less<T>为例,在STL中是大堆,我是以大堆实现的
堆的性质
堆具有以下性质
堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值;
堆总是⼀棵完全⼆叉树。
⼆叉树性质
对于具有 n 个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的数组顺序对所有结点从
0 开始编号,则对于序号为 i 的结点有:
- 若 i>0 , i 位置结点的双亲序号:(i-1)/2 ; i=0 , i 为根结点编号,⽆双亲结点
- 若 2i+1<n ,左孩⼦序号: 2i+1 ,2i+1>=n 否则⽆左孩⼦
- 若 2i+2<n ,右孩⼦序号: 2i+2 ,2i+2>=n 否则⽆右孩⼦
向上调整算法
向上调整算法
先将元素插⼊到堆的末尾,即最后⼀个孩⼦之后
插⼊之后如果堆的性质遭到破坏,将新插⼊结点顺着其双双亲往上调整到合适位置即可

void AdjustUp(int child)
{
less<T> com;
int parent = (child - 1) / 2;
while (child > 0)
{
if (_con[parent] < _con[child])
{
swap(_con[child], _con[parent]);
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
堆的向下调整算法
堆的删除
删除堆是删除堆顶的数据,将堆顶的数据根最后⼀个数据⼀换,然后删除数组最后⼀个数据,再进⾏
向下调整算法。

向下调整算法有⼀个前提:左右⼦树必须是⼀个堆,才能调整。
向下调整算法
将堆顶元素与堆中最后⼀个元素进⾏交换
删除堆中最后⼀个元素
将堆顶元素向下调整到满⾜堆特性为⽌

void AdjustDown(int parent)
{
less<T> com;
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;
}
}
}
priority_queue的模拟实现
成员函数 | 实现方法 |
---|---|
push | 在容器尾部插入元素后进行一次向上调整算法 |
pop | 将容器头部和尾部元素交换,再将尾部元素删除,最后从根结点开始进行一次向下调整算法 |
top | 返回容器的第0个元素 |
size | 返回容器的当前大小 |
empty | 判断容器是否为空 |
#pragma once
#include<vector>//优先队列默认使用 vector 作为底层容器
namespace wlw
{
// 定义一个仿函数 less,用于实现小于比较
// 仿函数是一个重载了函数调用运算符(),可像函数一样使用
template <class T>
struct less
{
bool operator() (const T& x, const T& y) const
{
return x < y;
}
};
// 定义一个仿函数 greater,用于实现大于比较
template <class T>
struct greater
{
bool operator() (const T& x, const T& y) const
{
return x > y;
}
};
// 定义一个模板类 priority_queue,实现优先队列的功能
// T 表示存储的元素类型
// Container 表示底层容器类型,默认使用 vector<T>
// Compare 表示比较器类型,默认使用 less<T>,即大顶堆
template<class T, class Container = std::vector<T>, class Compare = less<T>>
class priority_queue
{
public:
// 使用 default 关键字强制编译器生成默认构造函数
priority_queue() = default;//因为下面使用了初始化列表编译器不能默认生成
template <class InputIterator>// InputIterator 是一个模板参数,表示输入迭代器类型
priority_queue(InputIterator first, InputIterator last)
:_con(first, last)
{
// 建堆操作,从最后一个非叶子节点开始,依次进行向下调整
// 最后一个非叶子节点的索引为 (size - 1 - 1) / 2 其中size-1为最后一个索引位置
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(i);
}
}
// 向上调整
void AdjustUp(int child)
{
// 创建一个比较器对象
Compare com;
// 计算父节点的索引
int parent = (child - 1) / 2;
// 当子节点索引大于 0 时,继续调整
while (child > 0)
{
// 使用比较器对象比较父节点和子节点
if (com(_con[parent], _con[child]))
{
// 如果父节点小于子节点(根据比较器规则),则交换它们
std::swap(_con[child], _con[parent]);
// 更新子节点和父节点的索引
child = parent;
parent = (parent - 1) / 2;
}
else
{
// 如果父节点大于等于子节点,说明堆的性质已经满足,退出循环
break;
}
}
}
// 插入元素到优先队列中
void push(const T& x)
{
// 将元素添加到底层容器的末尾
_con.push_back(x);
// 调用 AdjustUp 函数进行向上调整,维护堆的性质
AdjustUp(_con.size() - 1);
}
// 向下调整函数,用于在删除堆顶元素后维护堆的性质
void AdjustDown(int parent)
{
// 创建一个比较器对象
Compare com;
// 计算左子节点的索引
size_t child = parent * 2 + 1;
// 当子节点索引小于容器大小时,继续调整
while (child < _con.size())
{
// 假设左子节点是较小(或较大,根据比较器规则)的节点
// 如果右子节点存在且右子节点比左子节点小(或大),则更新子节点索引为右子节点
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
++child;
}
// 使用比较器对象比较父节点和子节点
if (com(_con[parent], _con[child]))
{
// 如果父节点小于子节点(根据比较器规则),则交换它们
std::swap(_con[child], _con[parent]);
// 更新父节点和子节点的索引
parent = child;
child = parent * 2 + 1;
}
else
{
// 如果父节点大于等于子节点,说明堆的性质已经满足,退出循环
break;
}
}
}
// 删除堆顶元素
void pop()
{
// 交换堆顶元素和最后一个元素
std::swap(_con[0], _con[_con.size() - 1]);
// 删除最后一个元素
_con.pop_back();
// 调用 AdjustDown 函数进行向下调整,维护堆的性质
AdjustDown(0);
}
// 判断优先队列是否为空
bool empty()
{
// 调用底层容器的 empty 函数进行判断
return _con.empty();
}
// 获取堆顶元素的引用
const T& top()
{
// 返回底层容器的第一个元素
return _con[0];
}
// 获取优先队列中元素的数量
size_t size()
{
// 调用底层容器的 size 函数获取元素数量
return _con.size();
}
private:
// 底层容器,用于存储优先队列的元素
Container _con;
};
}