STL C++详解——priority_queue的使用和模拟实现 堆的使用
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 0return 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;};
}