【C++】stack和queue:优先级队列的使用及底层原理
目录
一 priority-queue 的介绍
1 priority-queue的文档介绍
2 priority-queue的介绍
二 priority-queue的使用
1 示例使用
2 核心接口实现
1 push
2 top
3 pop
4 empty
3 迭代器区间初始化
三 仿函数
1 仿函数的定义
2 仿函数可以像函数一样被使用
3 不同传参的区别
4 日期类举例
5 拓展 :sort && qsort
6 其他仿函数
1 remove remove_if
2 find_if
四 priority-queue完整代码
一 priority-queue 的介绍
1 priority-queue的文档介绍
priority-queue的文档介绍
翻译:
1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素 中最大的。
2. 此上下文类似于堆,在堆中可以随 时插入元素,并且只能检索最大堆元素(优先队列中位于顶 部的元素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue 提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的 顶部。
4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过 随机访问迭代器访问,并支持以下操作:
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素
5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue 类实例化指定容器类,则使用vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用 算法函数make_heap、push_heap和pop_heap来自动完成此操作。
2 priority-queue的介绍

priority_queue(优先队列)是 C++ 标准库中的一种容器适配器,它基于堆(heap) 数据结构实现,能够保证每次取出的元素都是当前队列中优先级最高的元素(默认是最大的元素)
第二个模板参数是容器适配器(容器适配器都不支持迭代器),第三个参数是仿函数(后面讲解)
核心接口:

二 priority-queue的使用
1 示例使用
#include<queue>
#include<iostream>
using namespace std;int main()
{//priority_queue<int> pq; //默认是大的优先级高(大堆)priority_queue<int, deque<int>, greater<int>> pq;//调整小的优先级高pq.push(3);pq.push(1);pq.push(5);pq.push(7);pq.push(2);while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;
}
输出结果为1,2,3,5,7 为小堆
2 核心接口实现
priority-queue的私有成员变量只有一个:
private:Container _con;
1 push
任何一个数组都可以看作完全二叉树----->算出下标关系

用Push需要用到向上调整算法,因为需要确保它依然是大堆,如果忘了的同学可以看博主之前这一篇复习一下:
【数据结构】二叉树和堆
void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}
从最后一个位置插入,开始向上调整算法:最后一个位置是size()-1

void adjust_up(int child)
{int parent = (child-1)/2;while(child > 0){if(_con[child] > _con[parent]){swap(_con[child] , _con[parent]);//C++算法库中有,不需要自己实现child = parent;//继续向上调整,把父亲给孩子parent = (child-1)/2;}else{break;}}
}

上图假设插入的数字是75,向上调整结束之后,75会在根节点的位置
注意:大堆只是父节点比孩子节点大,但是不一定是递减排序!
2 top
const T& top(){return _con[0];}
3 pop
先交换根节点和最后一个节点,尾删。之后从0节点开始向下遍历,确保删除之后依然是大堆
void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();//尾删adjust_down(0);//从0节点开始向下调整}

void adjust_down(inr parent)
{int child = parent*2+1;//左孩子while(child < _con.size()){if(child+1 < _con.szie() && _con[child+1] > _con[child]){++child;//默认左孩子大,但如果右孩子比左孩子大,则++child}if(_con[child] > _con[parent]){swap[_con[child],_con[parent]);parent = child;//继续向下调整child = parent*2+!;}else{break;}}
}
那怎么从大堆变成小堆:只用将第二个if语句的大于号换成小于号
4 empty
bool empty(){return _con.empty();}
3 迭代器区间初始化
priority-queue支持迭代器区间初始化,但是栈和队列都不支持
迭代器区间,可以传数组,也可以传指向数组的指针
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last):_con(first, last)
{// 建堆for (int i = (_con.size()-1-1)/2; i >= 0; i--)//向下调整算法建堆,从最后一个节点的父节点开始{adjust_down(i);}
}
三 仿函数
1 仿函数的定义
在 C++ 中,仿函数(Functor) 也称为函数对象(Function Object),是一种 “行为类似函数” 的对象。它的核心是通过在类或结构体中重载 operator() 运算符,使得该类的实例可以像普通函数一样被调用。
仿函数的本质
仿函数不是函数,而是一个对象,但它可以通过
对象名(参数)的形式调用,就像函数调用一样
仿函数就是一个类,只是仿函数重载了一个特殊的运算符:(),只要重载了这个运算符的类都可以称为仿函数
仿函数的使用在其他地方没有什么用,但是在这里,可以把他看成一个开关,当是小于号的时候是大堆,是大于号的时候是小堆
2 仿函数可以像函数一样被使用
#include <iostream>
using namespace std;template <class T>
struct Less
{bool operator() (const T& x, const T& y) const { return x < y; }
};int main()
{Less<int> less;cout << less(1, 2) << endl; // 方式1:像函数一样调用cout << less.operator()(1, 2) << endl; // 方式2:显式调用operator()return 0;
}
代码解释
-
仿函数(函数对象)的定义:
struct Less是一个模板结构体,内部重载了operator()运算符。这使得Less的对象可以像函数一样被调用,因此称为 “函数对象”。 -
核心逻辑:
operator()的功能是比较两个值x和y,返回x < y的结果(即true或false)。 -
使用方式:
- 先创建
Less<int>类型的对象less(指定模板参数为int,表示比较整数)。 - 两种调用方式:
less(1, 2):简化写法,编译器会自动转换为调用operator()。less.operator()(1, 2):显式调用重载的运算符
- 先创建
3 不同传参的区别
(1)priority-queue.h中
// 强制编译器生成默认构造函数
priority_queue() = default; // 注意:是default,不是defultvoid adjust_up(int child)
{Compare com; // 创建仿函数对象,用于比较int parent = (child - 1) / 2;while (child > 0){// 调用仿函数比较父节点和子节点if (com(_con[parent], _con[child])) // 注意:此处缺少闭合括号{swap(_con[child], _con[parent]); // 注意:是swap,不是sawpchild = parent;parent = (child - 1) / 2;}else{break;}}
}
(2)test.cpp
#include "priority_queue.h"int main()
{int a[] = { 30,4,2,66,3 };bit::priority_queue<int> pq(a, a+5);//建大堆//bit::priority_queue<int, vector<int>, bit::Greater<int>> pq(a, a + 5);//建小堆pq.push(3);pq.push(1);pq.push(5);pq.push(7);pq.push(2);while (!pq.empty()){cout << pq.top() << " ";pq.pop();}cout << endl;
}
注释那一行是建小堆,上一行是建大堆
由此我们可以看出,当要建大堆的时候,传一个参数,调用的是Less,建小堆的时候,传三个参数,调Greater
但是我们还需要实现less和Greater

4 日期类举例
// 仿函数
// 日期类:比较大小
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);}
};
int main()
{jq::priority_queue<Date*, vector<Date*>, PDateLess> pq; // 现在就不再是按指针去比较,而是按指针变量指向的内容去比较的// new出来的地址带有很强的随机性pq.push(new Date(2025, 10, 18));pq.push(new Date(2025, 10, 19));pq.push(new Date(2025, 10, 17));while (!pq.empty()){cout << *pq.top() << " ";pq.pop();}cout << endl;
}
但是我们运行出来之后发现每次运行的结果都不一样。这是因为new开辟的地址有很强的随机性,比较的时候是按照指针去比较,没有意义。所以我们需要自己去实现仿函数控制。
// 自定义仿函数:比较Date*指针指向的对象大小(按<比较)
struct PDateLess
{bool operator()(const Date* p1, const Date* p2) const{// 解引用指针,比较实际Date对象return *p1 < *p2; }
};
5 拓展 :sort && qsort

vector<int> v1 = {1,2,3,4,5,6}greater<int> gt; // 降序
// sort(v1.begin(), v1.end()); // 升序
// sort(v1.begin(), v1.end(), gt); //用仿函数比较更加灵活
sort(v1.begin(), v1.end(), greater<int>()); //加了()for (auto e : v1)
{cout << e << " ";
}
cout << endl;// 输出: 6 5 4 3 2 1
在C语言中,qsort是函数指针
看开口方向:< 升序 >降序
6 其他仿函数
1 remove remove_if


// remove: 查找 + 删除 (find + erase)// remove_if (也是一个仿函数)list<int> lt1 = { 1,6,1,7,3,8,9,3 };lt1.remove(1); // 默认的remove,给一个值进行删除for (auto e : lt1){cout << e << " ";}cout << endl;// 输出: 6 7 3 8 9 3return 0;
}
2 find_if

四 priority-queue完整代码
#pragma once
#include<vector>namespace bit
{// 默认大的优先级高template<class T, class Container = std::vector<T>>class priority_queue{public:template <class InputIterator>priority_queue(InputIterator first, InputIterator last):_con(first, last){// 建堆for (int i = (_con.size()-1-1)/2; i >= 0; i--){adjust_down(i);}}// 强制编译器生成默认构造priority_queue() = default;void adjust_up(int child){int parent = (child - 1) / 2;while (child > 0){if (_con[child] > _con[parent]){swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}}void adjust_down(int parent){size_t child = parent * 2 + 1;while (child < _con.size()){if (child + 1 < _con.size() && _con[child + 1] > _con[child]){++child;}if (_con[child] > _con[parent]){swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}void push(const T& x){_con.push_back(x);adjust_up(_con.size() - 1);}void pop(){swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}const T& top(){return _con[0];}bool empty(){return _con.empty();}private:Container _con;};
}
