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

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

二 priority-queue的使用
1 示例使用
cpp
#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的私有成员变量只有一个:
cpp
private:
Container _con;
1 push
任何一个数组都可以看作完全二叉树----->算出下标关系

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

cpp
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
cpp
const T& top()
{
return _con[0];
}
3 pop
先交换根节点和最后一个节点,尾删。之后从0节点开始向下遍历,确保删除之后依然是大堆
cpp
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();//尾删
adjust_down(0);//从0节点开始向下调整
}

cpp
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
cpp
bool empty()
{
return _con.empty();
}
3 迭代器区间初始化
priority-queue支持迭代器区间初始化,但是栈和队列都不支持
迭代器区间,可以传数组,也可以传指向数组的指针
cpp
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 仿函数可以像函数一样被使用
cpp
#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中
cpp
// 强制编译器生成默认构造函数
priority_queue() = default; // 注意:是default,不是defult
void 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,不是sawp
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
(2)test.cpp
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 日期类举例
cpp
// 仿函数
// 日期类:比较大小
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);
}
};
cpp
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开辟的地址有很强的随机性,比较的时候是按照指针去比较,没有意义。所以我们需要自己去实现仿函数控制。
cpp
// 自定义仿函数:比较Date*指针指向的对象大小(按<比较)
struct PDateLess
{
bool operator()(const Date* p1, const Date* p2) const
{
// 解引用指针,比较实际Date对象
return *p1 < *p2;
}
};
5 拓展 :sort && qsort

cpp
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


cpp
// 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 3
return 0;
}
2 find_if

四 priority-queue完整代码
cpp
#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;
};
}