priority_queue文档介绍翻译
优先队列是一种容器适配器,专门设计成其中的第一个元素始终是根据某种严格的弱排序准则最大的元素。
这种上下文类似于堆,其中元素可以在任何时刻插入,而只能检索最大堆元素(在优先队列中顶部的元素)。
优先队列被实现为容器适配器,这些适配器是使用特定容器类的封装对象作为其底层容器的类,提供一组特定的成员函数来访问其元素。元素从特定容器的“后面”弹出,这被称为优先队列的顶部。
底层容器可以是任何标准容器类模板或其他特定设计的容器类。容器必须通过随机访问迭代器进行访问,并支持以下操作:
empty()
size()
front()
push_back()
pop_back()
标准容器类
vector
和deque
满足这些要求。默认情况下,如果没有为特定优先队列类实例化指定容器类,则使用标准容器vector
。需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器会在需要时自动调用算法函数
make_heap
、push_heap
和pop_heap
来完成这一点。
简单使用
优先队列(Priority Queue)是一种特殊的队列,它的出队顺序是按照元素的优先级决定的,而不是元素进入队列的先后顺序。在C++中,优先队列通常通过STL中的priority_queue
容器来实现。priority_queue
在<queue>
头文件中定义,它可以让你轻松地实现一个能够根据元素优先级进行动态排序的队列。
基本操作
插入元素(push
):将一个元素添加到优先队列中。
获取顶部元素(top
):返回优先级最高的元素,但不从队列中移除它。
删除顶部元素(pop
):移除优先级最高的元素。
检查队列是否为空(empty
):检查优先队列是否没有任何元素。
获取队列的大小(size
):返回优先队列中的元素数量。
示例代码
下面是一个使用priority_queue
的基本示例:
#include <iostream>
#include <queue>
int main() {
// 创建一个int类型的优先队列,默认是最大堆
std::priority_queue<int> pq;
// 插入元素
pq.push(30);
pq.push(10);
pq.push(20);
pq.push(5);
// 循环直到优先队列为空
while (!pq.empty()) {
// 打印顶部元素
std::cout << pq.top() << " ";
// 移除顶部元素
pq.pop();
}
return 0;
}
最小堆优先队列
默认情况下,C++的priority_queue
使用std::less<T>
作为比较函数,这意味着它将实现为一个最大堆,优先级最高的(值最大的)元素会被放在队列的前面。
如果你想创建一个最小堆,使得优先级最低的元素先出队,你可以通过传递一个比较函数来实现。例如:
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
int main() {
// 创建一个最小堆优先队列
std::priority_queue<int, std::vector<int>, std::greater<int>> pq;
pq.push(30);
pq.push(10);
pq.push(20);
pq.push(5);
while (!pq.empty()) {
std::cout << pq.top() << " ";
pq.pop();
}
return 0;
}
优先队列默认创建最大堆
/*优先队列默认创建大堆*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
void TestPriorityQueue1()
{
// 默认按照less方式比较,创建的是大堆
priority_queue<int> q;
q.push(5);
q.push(4);
q.push(8);
q.push(1);
q.push(9);
q.push(2);
q.push(6);
q.push(3);
q.push(7);
q.push(0);
cout << q.size() << endl;
cout << q.top() << endl;
q.pop();
q.pop();
q.pop();
cout << q.size() << endl;
cout << q.top() << endl;
}
int main(){
TestPriorityQueue1();
}
#endif
优先队列主动创建最小堆
/*优先队列默认创建最小堆*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
// greater是STL提供的以大于方式比较两个元素的类模板
// 在使用时一定要<functional>的头文件
void TestPriorityQueue2()
{
// 如果要创建小堆,则元素必须按照大于的方式比较
priority_queue<int,vector<int>, greater<int>> q;
q.push(5);
q.push(4);
q.push(8);
q.push(1);
q.push(9);
q.push(2);
q.push(6);
q.push(3);
q.push(7);
q.push(0);
cout << q.size() << endl;
cout << q.top() << endl;
q.pop();
q.pop();
q.pop();
cout << q.size() << endl;
cout << q.top() << endl;
}
int main(){
TestPriorityQueue2();
}
#endif
优先队列日期类创建最大堆
/*优先队列日期类最大堆*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return _day < d._day;
}
bool operator>(const Date& d)const
{
return _day > d._day;
}
void Show()const{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
void TestPriorityQueue3()
{
priority_queue<Date> q;
Date d1(2023, 4, 22);
Date d2(2023, 4, 21);
Date d3(2023, 4, 23);
q.push(d1);
q.push(d2);
q.push(d3);
while (!q.empty()) {
q.top().Show();
q.pop();
}
}
int main(){
TestPriorityQueue3();
}
#endif
优先队列范围迭代器创建对象
/*优先队列范围迭代器创建对象*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
void TestPriorityQueue5()
{
vector<int> v{ 3,2,7,6,0,4,1,9,8,5 };
priority_queue<int> q(v.begin(), v.end());
}
int main(){
TestPriorityQueue5();
}
#endif
优先队列日期类创建最小堆
/*优先队列日期类创建最小堆*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return _day < d._day;
}
bool operator>(const Date& d)const
{
return _day > d._day;
}
void Show()const{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
void TestPriorityQueue4()
{
priority_queue<Date, vector<Date>, greater<Date>> q;
Date d1(2023, 4, 22);
Date d2(2023, 4, 21);
Date d3(2023, 4, 23);
q.push(d1);
q.push(d2);
q.push(d3);
while (!q.empty()) {
q.top().Show();
q.pop();
}
}
int main(){
TestPriorityQueue4();
}
#endif
自定义判断函数,函数指针
/*自定义判断函数,函数指针*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return _day < d._day;
}
bool operator>(const Date& d)const
{
return _day > d._day;
}
void Show()const{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
friend bool CompareDate(const Date* left, const Date* right);
friend class Compare;
private:
int _year;
int _month;
int _day;
};
bool CompareDate(const Date* left, const Date* right)
{
return left->_day < right->_day;
}
typedef bool (*COM)(const Date* left, const Date* right);
void TestPriorityQueue6()
{
priority_queue<Date*, vector<Date*>, COM> q(CompareDate);
Date d1(2023, 4, 22);
Date d2(2023, 4, 21);
Date d3(2023, 4, 23);
q.push(&d1);
q.push(&d2);
q.push(&d3);
while (!q.empty()) {
Date* top = q.top(); // 获取优先队列顶部元素
top->Show(); // 显示日期
q.pop(); // 移除顶部元素
}
}
int main(){
TestPriorityQueue6();
}
typedef bool (*COM)(const Date* left, const Date* right);
这行代码定义了一个函数指针类型COM
,用于指向一个特定的函数签名。这个签名要求函数接受两个指向Date
对象的常量指针作为参数,并返回一个bool
类型的结果。在这个上下文中,这样的函数通常用于比较两个Date
对象,并根据比较结果(真或假)来排序或做出决策。
具体来说,typedef
关键字在C++中用于为现有的类型定义一个新的名称。在这个例子中,它定义了COM
作为一种新的类型,这种类型是指向函数的指针,这些函数具有特定的参数列表和返回类型:
-
参数列表:两个
const Date*
类型的参数,表示指向常量Date
对象的指针。使用指针允许函数不改变原始对象的状态,同时使用const
确保函数内部不会修改这些对象。 -
返回类型:
bool
,用于表示比较的结果。在排序或其他需要比较的场景中,返回值通常用于指示第一个参数是否在排序顺序上位于第二个参数之前。
priority_queue<Date*, vector<Date*>, COM> q(CompareDate);
这行代码创建了一个 std::priority_queue
,其中包含了指向 Date
对象的指针。在这个 priority_queue
的声明中,使用了三个模板参数:
-
元素类型:
Date*
,表示队列中存储的元素是指向Date
对象的指针。 -
底层容器:
std::vector<Date*>
,表示优先队列使用std::vector
来存储这些指针。std::vector
是一个动态数组,适用于几乎所有类型的容器操作,而且在这里特别适合作为优先队列的底层数据结构。 -
比较类型:
COM
,这是一个指向函数的指针类型,用于比较两个Date
对象。在这个上下文中,COM
是通过之前定义的typedef
创建的,指向一个接受两个const Date*
类型参数的函数,并返回一个bool
类型的结果。
接下来的部分 q(CompareDate);
是 priority_queue
的构造函数调用,它接受一个名为 CompareDate
的函数作为参数。这个函数符合 COM
类型的要求,用于确定队列中元素的优先顺序。在这个例子中,CompareDate
函数基于日期对象的 _day
字段来比较日期,以决定它们在队列中的顺序。
这种方式允许自定义优先队列的排序准则,而不是仅依赖于元素类型的默认比较操作(例如,如果是基本类型或具有定义了 <
运算符的类型)。通过提供自定义比较函数,用户可以控制哪些元素应该被视为优先级更高,这在处理复杂数据类型或有特定排序需求的场合特别有用。
仿函数
在C++中,仿函数(Function Object或Functor)是一个行为类似函数的对象。具体来说,仿函数是通过重载 operator()
运算符的类实例,这让类的实例可以像函数一样被调用。仿函数提供了比普通函数指针更多的灵活性和功能,因为它们可以拥有自己的状态。
仿函数的优点
状态保持:与普通函数不同,仿函数可以有自己的状态。你可以在多次调用之间保持和修改这些状态。
内联调用:仿函数的调用可以被编译器内联,这意味着可以减少函数调用的开销,提高效率。
泛型编程:仿函数可以搭配标准模板库(STL)中的算法和容器使用,非常适合实现自定义的比较逻辑或操作。
示例:自定义比较仿函数
假设你想在 priority_queue
中使用自定义的比较逻辑,可以定义一个仿函数来实现这一点。以下是一个简化的 Date
类仿函数比较的例子:
#include <iostream>
#include <queue>
using namespace std;
class Date {
public:
int year, month, day;
Date(int y, int m, int d) : year(y), month(m), day(d) {}
};
// 自定义比较仿函数
class CompareDate {
public:
bool operator()(const Date& lhs, const Date& rhs) const {
// 比较逻辑:先年后月再日
if (lhs.year != rhs.year) return lhs.year > rhs.year;
if (lhs.month != rhs.month) return lhs.month > rhs.month;
return lhs.day > rhs.day;
}
};
int main() {
priority_queue<Date, vector<Date>, CompareDate> pq;
pq.push(Date(2023, 4, 22));
pq.push(Date(2023, 5, 21));
pq.push(Date(2023, 3, 23));
while (!pq.empty()) {
Date d = pq.top();
cout << d.year << "-" << d.month << "-" << d.day << endl;
pq.pop();
}
return 0;
}
在这个例子中,CompareDate
是一个仿函数,它重载了 operator()
来提供 Date
对象的比较逻辑。这个逻辑首先比较年份,如果年份相同,则比较月份,如果月份也相同,最后比较日。这个仿函数可以用作 priority_queue
的比较类型参数,从而定制容器中元素的排列顺序。
优先队列与仿函数结合
/*优先队列与仿函数结合*/
#if 1
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return _day < d._day;
}
bool operator>(const Date& d)const
{
return _day > d._day;
}
void Show()const{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
friend bool CompareDate(const Date* left, const Date* right);
friend class Compare;
private:
int _year;
int _month;
int _day;
};
bool CompareDate(const Date* left, const Date* right)
{
return left->_day < right->_day;
}
typedef bool (*COM)(const Date* left, const Date* right);
// 仿函数--函数对象:可以像函数一样使用的对象
class Compare
{
public:
bool operator()(const Date* left, const Date* right)
{
return left->_day < right->_day;
}
};
void TestPriorityQueue7()
{
Date d1(2023, 4, 22);
Date d2(2023, 4, 21);
Date d3(2023, 4, 23);
CompareDate(&d1, &d2);
Compare com;
com(&d1, &d2);
com.operator()(&d1, &d2);
priority_queue<Date*, vector<Date*>, Compare> q;
q.push(&d1);
q.push(&d2);
q.push(&d3);
while (!q.empty()) {
Date* top = q.top(); // 获取优先队列顶部元素
top->Show(); // 显示日期
q.pop(); // 移除顶部元素
}
}
int main()
{
TestPriorityQueue7();
return 0;
}
#endif
对于优先队列比较参数的探究
priority_queue<Date*, vector<Date*>, Compare> q;//仿函数
priority_queue<Date, vector<Date>, CompareDate> pq;//仿函数
priority_queue<Date*, vector<Date*>, COM> q(CompareDate);//函数指针
priority_queue<Date, vector<Date>, greater<Date>> q; //默认greater对象
我们发现第三个参数,比较大小的参数,可以有上面三种使用方法。第一个和第二个是一样的,都是仿函数,仿函数本质是对象,和第四个同样是传入对象,而第三个传入的是函数指针。说明第三个参数可以接收函数指针也可以接收对象。
仿函数和函数指针的区别
虽然从表面上看,仿函数(通过类重载的 operator()
)和函数指针(指向一个具体函数的指针)都可以作为排序准则传递给 std::priority_queue
(或其他模板类),它们在底层实现和使用方式上存在一些关键差异:
仿函数(Function Objects):仿函数是通过类实例化的对象,这些对象通过重载 operator()
来实现。它们可以有自己的状态(成员变量)并可以携带更多的上下文信息。在模板参数中传递仿函数类型时,你实际上传递的是一个类型(而非对象实例本身),std::priority_queue
在内部会创建这个类型的实例来进行元素比较。如果需要传递状态或参数给仿函数,你需要在仿函数类中提供相应的构造函数。
函数指针:函数指针是指向函数的指针,它不能携带状态(除非使用全局变量或静态变量,但这通常不是一个好的设计选择)。在模板参数中使用函数指针时,如 COM
类型,你实际上是在传递一个指向函数的指针,这意味着传递的是一个地址值。当你在构造 std::priority_queue
时提供 CompareDate
函数作为参数,你实际上是在提供一个具体的函数指针值。
对象与地址的传递
对于仿函数,你传递的是一个类型,std::priority_queue
使用这个类型在内部构造一个对象实例。这个过程并不涉及显式的地址传递,而是类型信息的传递。
对于函数指针,传递的确实是一个地址值,即函数在内存中的位置。
默认比较对象
当使用如 std::greater<Date>
这样的默认比较对象时,同样是传递了一个类型给 std::priority_queue
,它会构造这个类型的实例来进行比较。std::greater<T>
和 std::less<T>
是标准库提供的比较函数对象类型,它们也是通过重载 operator()
实现的。
优先队列创建对象的方式
使用默认比较函数对象
默认情况下,std::priority_queue
使用std::less<T>
作为其比较函数对象,构造一个最大堆。这意味着没有指定比较函数时,队列中的最大元素将会位于顶部。
std::priority_queue<int> pq; // 默认为最大堆,使用 std::less<int> 比较元素
使用标准库比较函数对象
可以通过显式指定比较函数对象(如std::greater<T>
)来改变优先队列的排序行为,例如构建一个最小堆。
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
使用自定义仿函数
通过定义一个自定义的比较类(仿函数),可以实现更复杂的比较逻辑。该类需要重载operator()
。
class Compare {
public:
bool operator()(int a, int b) {
// 自定义比较逻辑
return a > b; // 示例:构建最小堆
}
};
std::priority_queue<int, std::vector<int>, Compare> customHeap;
使用函数指针
如果你有一个静态比较函数,也可以将其地址作为优先队列的比较逻辑。这通常适用于简单的比较逻辑或C风格的代码。
bool compare(int a, int b) { return a > b; } // 函数指针比较
// 注意:直接使用函数指针作为模板参数在标准的 priority_queue 中不直接支持,需要包装或转换为对象。
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!