前言:
priority_queue (优先队列) 是计算机科学中非常常用且强大的数据结构,尤其在 C++ 标准模板库 (STL) 中被广泛使用。
它属于“容器适配器”,这意味着它不是从零开始构建的,而是封装了其他底层容器(例如 vector),只提供特定的接口来限制元素的访问方式。

一、前置知识
1.1 仿函数的简介
仿函数 (Functor),也被称为函数对象 (Function Object)。
用一句话来概括:它本质上是一个“类 (Class)”或“结构体 (Struct)”,但你可以像调用“普通函数”一样去调用它的对象。
1.2 仿函数的核心原理
一个对象之所以能像函数一样被调用,是因为这个 “类 (Class)” 或 “结构体 (Struct)” 重载了函数调用运算符。
A. 仿函数的基本长相
代码示例:要创建一个仿函数,你只需要定义一个类(或结构体),并在其中实现 operator() 方法。
#include <iostream>
// 定义一个仿函数类
struct Multiplier
{
// 重载 operator()
int operator()(int a, int b) const
{
return a * b;
}
};
int main()
{
Multiplier multiply; // 实例化一个对象
// 像调用函数一样使用这个对象
int result = multiply(3, 4);
std::cout << "Result: " << result << std::endl; // 输出 12
return 0;
}
B. 普通函数与仿函数
普通函数与仿函数的调用很类似,具体样式如下:
普通函数调用:
void printHello()
{
std::cout << "Hello!" << std::endl;
}
int main()
{
// 调用
printHello();
return 0;
}
仿函数调用:
struct Functor
{
// 重载 () 运算符
void operator() ()
{
std::cout << "Hello from Functor!" << std::endl;
}
};
int main()
{
// 调用
Functor myPrinter; // 1. 先创建一个对象
myPrinter(); // 2. 像函数一样调用这个对象!
}
1.3 仿函数的优势
A. 仿函数可以“拥有状态”
普通函数在执行完毕后,局部变量就销毁了,如果想让普通函数记住某些状态,通常只能用全局变量或静态局部变量。
仿函数是一个类,它可以有成员变量,从而能够在多次调用之间保存和传递状态。
代码如下所示:
struct Adder
{
int base_value; // 状态:基础值
// 构造函数初始化状态
Adder(int val) : base_value(val) {}
int operator()(int x) const
{
return x + base_value;
}
};
int main()
{
Adder add_five(5); // 创建一个“总是加5”的仿函数
Adder add_ten(10); // 创建一个“总是加10”的仿函数
std::cout << add_five(10) << std::endl; // 输出 15
std::cout << add_ten(10) << std::endl; // 输出 20
}
B. 在 STL 模板中性能更好,且方便作为类型传递
像 priority_queue、std::sort 等 C++ 标准库组件,大量依赖模板。
如果你传一个普通函数指针给 std::sort,编译器很难对它进行“内联优化 (Inline)”,每次比较都会有函数调用的开销。
如果你传一个仿函数的类型,编译器在编译时就能确切知道你要调用的是哪一段代码,从而直接将代码内联展开,运行速度更快。
经典应用场景:结合 STL 使用
代码示例:假设我们要对一个整数数组进行降序排序
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 定义一个用于降序比较的仿函数
struct DescendingOrder
{
bool operator()(int a, int b) const
{
return a < b;
}
};
void bubbleSort(vector<int>& v,DescendingOrder Less)
{
for (int i =0; i<v.size()-1;i++)
{
for (int j = 0; j <v.size()-1; j++)
{
//传统写法
//if (v[j] < v[j + 1])
//{
// swap(v[j], v[j + 1]);
//}
//利用仿函数
if (Less(v[j], v[j + 1]))
{
swap(v[j], v[j + 1]);
}
}
}
}
int main()
{
vector<int> nums = { 3, 1, 4, 1, 5, 9 };
DescendingOrder Less;
bubbleSort(nums,Less);
for (int n : nums)
{
cout << n << " "; // 输出: 9 5 4 3 1 1
}
}
二、priority_queue
2.1 priority_queue的介绍
在 C++ 的STL 中,priority_queue 默认是基于堆 (Heap) 这种数据结构来实现的,通常是一个大顶堆。
1. 定义在头文件 <queue>
2. 底层:基于数据结构中的堆
3. 核心特性:优先队列中的元素被赋予了优先级,无论元素是以什么顺序进入队列的,每次被弹出(取出)的永远是优先级最高的那个元素。
2.2 priority_queue的定义方式
priority_queue 的类模板声明
template <class T , class Container = vector<T> , class Compare = less<T> > class priority_queue;
1. class T
解释:T 代表这个栈里要装的数据类型。
例如:当你写 priority_queue<int> 时,编译器就把这里的 T 全部替换成 int,如果你写 priority_queue<std::string>,T 就是字符串类型。
2.class Container = vector<T>
解释:
① class Container:这指明了底层具体是用什么数据结构来存数据的。
priority_queue 本身不直接管理内存,它是一个“包工头”,把具体存取数据的工作外包给了 Container。
② = vector<T> (默认模板参数):如果在定义priority_queue时候,你只提供存什么数据(T),
而没指定怎么存(Container),编译器就会默认使用 std::vector<T>作为底层的存储结构。
3. class Compare = less<T>
解释:
① class Compare: 它是一个函数对象(仿函数/Functor),用于比较两个元素的大小。
② = less<T> (默认模板参数) :less<T> 是 C++ 标准库(包含在 <functional> 头文件中)提供的一个内置仿函数
简单来说:它就是一个系统帮你写好的、专门用来判断“左边是否小于右边 (
<)”的工具。
注:
反直觉的重点: less<T> 的字面意思是“小于”,但它在优先队列中的作用是把最大的元素放在队首,也就是形成一个大顶堆 (Max-Heap)。
核心原因:less(父节点, 新节点),意思是:父节点 < 新节点,父节点得给新节点让位(下沉),
让大的新节点上浮,导致最大的节点一路畅通无阻地浮到了最顶端(根节点),从而形成了大顶堆。
改为小顶堆: 如果你想让最小的元素优先级最高(放在队首),需要将其替换为 std::greater<T>。
核心原因:greater(父节点, 新节点),意思是 父节点 > 新节点,父节点得给新节点让位(下沉)
让小的新节点上浮,导致最小的节点一路畅通无阻地浮到了最顶端(根节点),从而形成了小顶堆。
场景 1:基础数据类型的“大顶堆”
我们要存储 int,并且希望数字越大,优先级越高,则使用默认的“大顶堆”
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
// 1. T 是 int
// 2. Container 是 vector<int>
// 3. Compare 不写默认为大顶堆
priority_queue<int, vector<int>> max_pq;
max_pq.push(5);
max_pq.push(1);
max_pq.push(9);
while (!max_pq.empty())
{
cout <<"当前堆顶为:" << max_pq.top() << endl;
max_pq.pop();
}
return 0;
}
场景 2:基础数据类型的“大顶堆”
我们要存储 int,并且希望数字越小,优先级越高,此时必须写全三个参数:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
// 1. T 是 int
// 2. Container 是 vector<int>
// 3. Compare 是 greater<int> (小顶堆)
priority_queue<int, vector<int>, greater<int>> min_pq;
min_pq.push(5);
min_pq.push(1);
min_pq.push(9);
while (!min_pq.empty())
{
cout <<"当前堆顶为:" << min_pq.top() << endl;
min_pq.pop();
}
return 0;
}
场景 3:存储自定义结构体 (Struct)
如果我们队列里存的是自定义的结构体(比如:游戏中的怪物,按照威胁等级排序),我们需要自己告诉 priority_queue 如何比较它们。
方法 A:重载 < 运算符 (最常用) 如果在结构体内部重载了 <,就可以直接使用默认的 less<T>,只传第一个参数即可。
代码示例:
#include <iostream>
#include <queue>
using namespace std;
struct Monster
{
string name;
int threatLevel;
// 重载 < 运算符
// 注意:如果是 less,想让 threatLevel 大的在前面,就按照正常的 < 逻辑写即可
bool operator<(const Monster& other) const
{
return this->threatLevel < other.threatLevel;
}
};
int main()
{
// 只需提供类型,使用默认的 vector 和 less
// 1. T 是 struct Monster
// 2. Container 是 vector<Monster>
// 3. Compare 是 greater<int> (小顶堆)
priority_queue<Monster> pq;
pq.push({ "Goblin", 10 });
pq.push({ "Dragon", 99 });
pq.push({ "Slime", 1 });
cout << "威胁等级排名第一: " << pq.top().name << endl; // 输出: 先打: Dragon
return 0;
}
方法 B:自定义仿函数 (Functor) 如果你不想修改结构体本身的代码,可以利用第三个模板参数 Compare,写一个自定义的比较类。
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
struct Node
{
int x, y, cost;
};
// 自定义比较类
struct CompareNode
{
bool operator()(const Node& a, const Node& b)
{
// 如果想让 cost 小的排在前面(小顶堆),就用 >
return a.cost > b.cost;
}
};
int main()
{
// 只需提供类型,使用默认的 vector 和 less
// 1. T 是 struct Node
// 2. Container 是 vector<Node>
// 3. Compare 是 CompareNode (小顶堆)
// 必须写全三个参数,第三个参数传入我们自定义的 CompareNode
priority_queue<Node, vector<Node>, CompareNode> pq;
pq.push({ 0, 0, 50 });
pq.push({ 1, 2, 10 });
pq.push({ 3, 3, 20 });
cout << "最低消耗: " << pq.top().cost <<endl; // 输出: 最低消耗: 10
return 0;
}
2.3 priority_queue 的常用函数
2.3.1 push
模板参数:template<class T>
函数原型:void push (const T& val);
函数功能:在priority_queue 中插入一个新元素,并维持堆的性质。
代码示例:
// priority_queue::push/pop
#include <iostream> // std::cout
#include <queue> // std::priority_queue
int main()
{
//默认为大根堆
std::priority_queue<int> mypq;
mypq.push(30);
mypq.push(100);
mypq.push(25);
mypq.push(40);
std::cout << "Popping out elements..."<<std::endl;
while (!mypq.empty())
{
std::cout<< mypq.top() <<" ";
mypq.pop();
}
std::cout << '\n';
return 0;
}
输出结果:

2.3.2 pop
函数原型:void pop();
函数功能:移除堆顶元素,并维持堆的性质。
代码示例:
// priority_queue::push/pop
#include <iostream> // std::cout
#include <queue> // std::priority_queue
int main()
{
//默认为大根堆
std::priority_queue<int> mypq;
mypq.push(30);
mypq.push(100);
mypq.push(25);
mypq.push(40);
std::cout << "Popping out elements..."<<std::endl;
while (!mypq.empty())
{
std::cout<< mypq.top() <<" ";
mypq.pop();
}
std::cout << '\n';
return 0;
}
输出结果:

2.3.3 top
模板参数:template<class T>
函数原型:
①T & top();
②const T& top() const;
函数功能:返回对priority_queue 中顶部元素的常量引用。
代码示例:
// priority_queue::top
#include <iostream> // std::cout
#include <queue> // std::priority_queue
int main()
{
std::priority_queue<int> mypq;
mypq.push(10);
mypq.push(20);
mypq.push(15);
std::cout << "mypq.top() is now " << mypq.top() << '\n';
return 0;
}
输出结果如下:

2.3.4 empty
函数原型:bool empty() const;
函数功能:判断 priority_queue 是否为空
代码示例:
// priority_queue::empty
#include <iostream> // std::cout
#include <queue> // std::priority_queue
int main ()
{
std::priority_queue<int> mypq;
int sum = 0;
for (int i=1;i<=10;i++) mypq.push(i);
while (!mypq.empty())
{
sum += mypq.top();
mypq.pop();
}
std::cout << "total: " << sum << '\n';
return 0;
}
输出结果为:

2.3.5 size
函数原型: size_type size() const;
函数功能:返回 priority_queue 中的元素数量。
代码示例:
// priority_queue::size
#include <iostream> // std::cout
#include <queue> // std::priority_queue
int main()
{
std::priority_queue<int> myints;
std::cout << "0. size: " << myints.size() << '\n';
for (int i = 0; i < 5; i++) myints.push(i);
std::cout << "1. size: " << myints.size() << '\n';
myints.pop();
std::cout << "2. size: " << myints.size() << '\n';
return 0;
}
输出结果如下:

既然看到这里了,不妨关注+点赞+收藏,感谢大家,若有问题请指正。


498

被折叠的 条评论
为什么被折叠?



